blob: aeb46abc52d96968369f684d9cd2c6010f84a2d3 [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"
18
19#include <nghttp2/nghttp2.h>
20#include <unistd.h>
21
22#include <boost/asio/buffer.hpp>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080023#include <boost/asio/ssl/stream.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080024#include <boost/beast/core/error.hpp>
25#include <boost/beast/http/field.hpp>
26#include <boost/beast/http/fields.hpp>
27#include <boost/beast/http/message.hpp>
28#include <boost/beast/http/verb.hpp>
29#include <boost/optional/optional.hpp>
Ed Tanousd0882182024-01-26 23:45:25 -080030#include <boost/system/error_code.hpp>
Abiola Asojod23d6342025-06-18 20:15:24 +000031#include <boost/url/url_view.hpp>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080032
Ed Tanousd0882182024-01-26 23:45:25 -080033#include <array>
Ed Tanousd7857202025-01-28 15:32:26 -080034#include <bit>
35#include <cstddef>
36#include <cstdint>
Ed Tanousd0882182024-01-26 23:45:25 -080037#include <functional>
Ed Tanousd7857202025-01-28 15:32:26 -080038#include <map>
Ed Tanousd0882182024-01-26 23:45:25 -080039#include <memory>
Ed Tanousd7857202025-01-28 15:32:26 -080040#include <optional>
41#include <span>
Ed Tanous89cda632024-04-16 08:45:54 -070042#include <string>
Ed Tanousd7857202025-01-28 15:32:26 -080043#include <string_view>
Ed Tanous796ba932020-08-02 04:29:21 +000044#include <type_traits>
Ed Tanousd7857202025-01-28 15:32:26 -080045#include <utility>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080046#include <vector>
47
48namespace crow
49{
50
51struct Http2StreamData
52{
Jonathan Doman102a4cd2024-04-15 16:56:23 -070053 std::shared_ptr<Request> req = std::make_shared<Request>();
Ed Tanous325310d2024-03-15 09:05:04 -070054 std::optional<bmcweb::HttpBody::reader> reqReader;
Ed Tanous89cda632024-04-16 08:45:54 -070055 std::string accept;
Ed Tanousb2539062024-03-12 16:58:35 -070056 std::string acceptEnc;
Ed Tanous47f29342024-03-19 12:18:06 -070057 Response res;
Ed Tanousb2896142024-01-31 15:25:47 -080058 std::optional<bmcweb::HttpBody::writer> writer;
Ed Tanousfca2cbe2021-01-28 14:49:59 -080059};
60
61template <typename Adaptor, typename Handler>
62class HTTP2Connection :
63 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
64{
65 using self_type = HTTP2Connection<Adaptor, Handler>;
66
67 public:
Ed Tanous2e3cdf82025-08-01 09:49:35 -070068 HTTP2Connection(
69 boost::asio::ssl::stream<Adaptor>&& adaptorIn, Handler* handlerIn,
70 std::function<std::string()>& getCachedDateStrF, HttpType httpTypeIn,
71 const std::shared_ptr<persistent_data::UserSession>& mtlsSessionIn) :
Ed Tanousebe4c572025-02-08 14:29:53 -080072 httpType(httpTypeIn), adaptor(std::move(adaptorIn)),
73 ngSession(initializeNghttp2Session()), handler(handlerIn),
Ed Tanous2e3cdf82025-08-01 09:49:35 -070074 getCachedDateStr(getCachedDateStrF), mtlsSession(mtlsSessionIn)
Ed Tanousfca2cbe2021-01-28 14:49:59 -080075 {}
76
77 void start()
78 {
79 // Create the control stream
Ed Tanousf42e8592023-08-25 10:47:44 -070080 streams[0];
Ed Tanousfca2cbe2021-01-28 14:49:59 -080081
82 if (sendServerConnectionHeader() != 0)
83 {
Ed Tanous62598e32023-07-17 17:06:25 -070084 BMCWEB_LOG_ERROR("send_server_connection_header failed");
Ed Tanousfca2cbe2021-01-28 14:49:59 -080085 return;
86 }
87 doRead();
88 }
89
Ed Tanouscd7dbb32025-02-01 12:37:56 -080090 void startFromSettings(std::string_view http2UpgradeSettings)
91 {
92 int ret = ngSession.sessionUpgrade2(http2UpgradeSettings,
93 false /*head_request*/);
94 if (ret != 0)
95 {
96 BMCWEB_LOG_ERROR("Failed to load upgrade header");
97 return;
98 }
99 // Create the control stream
100 streams[0];
101
102 if (sendServerConnectionHeader() != 0)
103 {
104 BMCWEB_LOG_ERROR("send_server_connection_header failed");
105 return;
106 }
107 doRead();
108 }
109
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800110 int sendServerConnectionHeader()
111 {
Ed Tanous62598e32023-07-17 17:06:25 -0700112 BMCWEB_LOG_DEBUG("send_server_connection_header()");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800113
114 uint32_t maxStreams = 4;
115 std::array<nghttp2_settings_entry, 2> iv = {
116 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
117 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
118 int rv = ngSession.submitSettings(iv);
119 if (rv != 0)
120 {
Ed Tanous62598e32023-07-17 17:06:25 -0700121 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800122 return -1;
123 }
Ed Tanousd0882182024-01-26 23:45:25 -0800124 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800125 return 0;
126 }
127
Patrick Williams504af5a2025-02-03 14:29:03 -0500128 static ssize_t fileReadCallback(
129 nghttp2_session* /* session */, int32_t streamId, uint8_t* buf,
130 size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/,
131 void* userPtr)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800132 {
Ed Tanousf42e8592023-08-25 10:47:44 -0700133 self_type& self = userPtrToSelf(userPtr);
134
135 auto streamIt = self.streams.find(streamId);
136 if (streamIt == self.streams.end())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800137 {
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800138 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
139 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700140 Http2StreamData& stream = streamIt->second;
Ed Tanous62598e32023-07-17 17:06:25 -0700141 BMCWEB_LOG_DEBUG("File read callback length: {}", length);
Ed Tanousd547d8d2024-03-16 18:04:41 -0700142 if (!stream.writer)
143 {
144 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
145 }
Ed Tanous52e31622024-01-23 16:31:11 -0800146 boost::beast::error_code ec;
147 boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
148 stream.writer->getWithMaxSize(ec, length);
149 if (ec)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700150 {
Ed Tanous325310d2024-03-15 09:05:04 -0700151 BMCWEB_LOG_CRITICAL("Failed to get buffer");
Ed Tanous27b0cf92023-08-07 12:02:40 -0700152 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
153 }
Ed Tanous52e31622024-01-23 16:31:11 -0800154 if (!out)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800155 {
Ed Tanous325310d2024-03-15 09:05:04 -0700156 BMCWEB_LOG_ERROR("Empty file, setting EOF");
Ed Tanous52e31622024-01-23 16:31:11 -0800157 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
158 return 0;
159 }
160
161 BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
162 if (length < out->first.size())
163 {
Ed Tanous325310d2024-03-15 09:05:04 -0700164 BMCWEB_LOG_CRITICAL(
165 "Buffer overflow that should never happen happened");
Ed Tanous52e31622024-01-23 16:31:11 -0800166 // Should never happen because of length limit on get() above
167 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
168 }
Ed Tanous325310d2024-03-15 09:05:04 -0700169 boost::asio::mutable_buffer writeableBuf(buf, length);
Ed Tanous52e31622024-01-23 16:31:11 -0800170 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
Ed Tanous325310d2024-03-15 09:05:04 -0700171 size_t copied = boost::asio::buffer_copy(writeableBuf, out->first);
172 if (copied != out->first.size())
173 {
174 BMCWEB_LOG_ERROR(
175 "Couldn't copy all {} bytes into buffer, only copied {}",
176 out->first.size(), copied);
177 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
178 }
Ed Tanous52e31622024-01-23 16:31:11 -0800179
180 if (!out->second)
181 {
Ed Tanous325310d2024-03-15 09:05:04 -0700182 BMCWEB_LOG_DEBUG("Setting EOF flag");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800183 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800184 }
Ed Tanous325310d2024-03-15 09:05:04 -0700185 return static_cast<ssize_t>(copied);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800186 }
187
188 nghttp2_nv headerFromStringViews(std::string_view name,
Ed Tanous52e31622024-01-23 16:31:11 -0800189 std::string_view value, uint8_t flags)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800190 {
191 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
192 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
Ed Tanous52e31622024-01-23 16:31:11 -0800193 return {nameData, valueData, name.size(), value.size(), flags};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800194 }
195
196 int sendResponse(Response& completedRes, int32_t streamId)
197 {
Ed Tanous62598e32023-07-17 17:06:25 -0700198 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800199
200 auto it = streams.find(streamId);
201 if (it == streams.end())
202 {
203 close();
204 return -1;
205 }
Ed Tanous499b5b42024-04-06 08:39:18 -0700206 Http2StreamData& stream = it->second;
207 Response& res = stream.res;
208 res = std::move(completedRes);
Ed Tanous499b5b42024-04-06 08:39:18 -0700209
Ed Tanousb2539062024-03-12 16:58:35 -0700210 completeResponseFields(stream.accept, stream.acceptEnc, res);
Ed Tanous499b5b42024-04-06 08:39:18 -0700211 res.addHeader(boost::beast::http::field::date, getCachedDateStr());
Abiola Asojod23d6342025-06-18 20:15:24 +0000212 boost::urls::url_view urlView;
213 if (stream.req != nullptr)
214 {
215 urlView = stream.req->url();
216 }
217 res.preparePayload(urlView);
Ed Tanous499b5b42024-04-06 08:39:18 -0700218
219 boost::beast::http::fields& fields = res.fields();
220 std::string code = std::to_string(res.resultInt());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800221 std::vector<nghttp2_nv> hdr;
Ed Tanous52e31622024-01-23 16:31:11 -0800222 hdr.emplace_back(
223 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800224 for (const boost::beast::http::fields::value_type& header : fields)
225 {
Ed Tanous52e31622024-01-23 16:31:11 -0800226 hdr.emplace_back(headerFromStringViews(
Ed Tanousd0882182024-01-26 23:45:25 -0800227 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800228 }
Ed Tanousb2896142024-01-31 15:25:47 -0800229 http::response<bmcweb::HttpBody>& fbody = res.response;
Ed Tanous52e31622024-01-23 16:31:11 -0800230 stream.writer.emplace(fbody.base(), fbody.body());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800231
232 nghttp2_data_provider dataPrd{
Ed Tanousf42e8592023-08-25 10:47:44 -0700233 .source = {.fd = 0},
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800234 .read_callback = fileReadCallback,
235 };
236
237 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
238 if (rv != 0)
239 {
Ed Tanous62598e32023-07-17 17:06:25 -0700240 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800241 close();
242 return -1;
243 }
Ed Tanousd0882182024-01-26 23:45:25 -0800244 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800245
246 return 0;
247 }
248
249 nghttp2_session initializeNghttp2Session()
250 {
251 nghttp2_session_callbacks callbacks;
252 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
253 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
254 callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
255 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
Ed Tanous325310d2024-03-15 09:05:04 -0700256 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800257
258 nghttp2_session session(callbacks);
259 session.setUserData(this);
260
261 return session;
262 }
263
264 int onRequestRecv(int32_t streamId)
265 {
Ed Tanous62598e32023-07-17 17:06:25 -0700266 BMCWEB_LOG_DEBUG("on_request_recv");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800267
268 auto it = streams.find(streamId);
269 if (it == streams.end())
270 {
271 close();
272 return -1;
273 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700274 auto& reqReader = it->second.reqReader;
275 if (reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700276 {
277 boost::beast::error_code ec;
Ed Tanousdaadfb22024-12-20 09:25:54 -0800278 bmcweb::HttpBody::reader::finish(ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700279 if (ec)
280 {
281 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
282 close();
283 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
284 }
285 }
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700286 crow::Request& thisReq = *it->second.req;
Ed Tanousb2539062024-03-12 16:58:35 -0700287 using boost::beast::http::field;
288 it->second.accept = thisReq.getHeaderValue(field::accept);
289 it->second.acceptEnc = thisReq.getHeaderValue(field::accept_encoding);
Ed Tanous89cda632024-04-16 08:45:54 -0700290
Ed Tanous62598e32023-07-17 17:06:25 -0700291 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
292 thisReq.url().encoded_path());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800293
Ed Tanousf42e8592023-08-25 10:47:44 -0700294 crow::Response& thisRes = it->second.res;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800295
296 thisRes.setCompleteRequestHandler(
297 [this, streamId](Response& completeRes) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400298 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
299 if (sendResponse(completeRes, streamId) != 0)
300 {
301 close();
302 return;
303 }
304 });
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800305 auto asyncResp =
Ed Tanousf42e8592023-08-25 10:47:44 -0700306 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
Ed Tanous83328312024-05-09 15:48:09 -0700307 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
Ed Tanous325310d2024-03-15 09:05:04 -0700308 {
Ed Tanous83328312024-05-09 15:48:09 -0700309 thisReq.session = crow::authentication::authenticate(
Ed Tanous2e3cdf82025-08-01 09:49:35 -0700310 {}, asyncResp->res, thisReq.method(), thisReq.req, mtlsSession);
Ed Tanous83328312024-05-09 15:48:09 -0700311 if (!crow::authentication::isOnAllowlist(thisReq.url().path(),
312 thisReq.method()) &&
313 thisReq.session == nullptr)
Ed Tanous499b5b42024-04-06 08:39:18 -0700314 {
Ed Tanous83328312024-05-09 15:48:09 -0700315 BMCWEB_LOG_WARNING("Authentication failed");
316 forward_unauthorized::sendUnauthorized(
317 thisReq.url().encoded_path(),
318 thisReq.getHeaderValue("X-Requested-With"),
319 thisReq.getHeaderValue("Accept"), asyncResp->res);
320 return 0;
Ed Tanous499b5b42024-04-06 08:39:18 -0700321 }
Ed Tanous325310d2024-03-15 09:05:04 -0700322 }
Ed Tanous83328312024-05-09 15:48:09 -0700323 std::string_view expected =
324 thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
325 BMCWEB_LOG_DEBUG("Setting expected hash {}", expected);
326 if (!expected.empty())
327 {
328 asyncResp->res.setExpectedHash(expected);
329 }
330 handler->handle(it->second.req, asyncResp);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800331 return 0;
332 }
333
Ed Tanous325310d2024-03-15 09:05:04 -0700334 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
335 const uint8_t* data, size_t len)
336 {
337 auto thisStream = streams.find(streamId);
338 if (thisStream == streams.end())
339 {
340 BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
341 close();
342 return -1;
343 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700344
345 std::optional<bmcweb::HttpBody::reader>& reqReader =
346 thisStream->second.reqReader;
347 if (!reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700348 {
Ed Tanous8e5cc7b2024-04-02 11:00:54 -0700349 reqReader.emplace(
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700350 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
351 thisStream->second.req->req.body()));
Ed Tanous325310d2024-03-15 09:05:04 -0700352 }
353 boost::beast::error_code ec;
Ed Tanousd547d8d2024-03-16 18:04:41 -0700354 reqReader->put(boost::asio::const_buffer(data, len), ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700355 if (ec)
356 {
357 BMCWEB_LOG_CRITICAL("Failed to write payload");
358 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
359 }
360 return 0;
361 }
362
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400363 static int onDataChunkRecvStatic(
364 nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
365 const uint8_t* data, size_t len, void* userData)
Ed Tanous325310d2024-03-15 09:05:04 -0700366 {
367 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
368 if (userData == nullptr)
369 {
370 BMCWEB_LOG_CRITICAL("user data was null?");
371 return NGHTTP2_ERR_CALLBACK_FAILURE;
372 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400373 return userPtrToSelf(userData).onDataChunkRecvCallback(
374 flags, streamId, data, len);
Ed Tanous325310d2024-03-15 09:05:04 -0700375 }
376
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800377 int onFrameRecvCallback(const nghttp2_frame& frame)
378 {
Ed Tanous62598e32023-07-17 17:06:25 -0700379 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800380 switch (frame.hd.type)
381 {
382 case NGHTTP2_DATA:
383 case NGHTTP2_HEADERS:
384 // Check that the client request has finished
385 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
386 {
387 return onRequestRecv(frame.hd.stream_id);
388 }
389 break;
390 default:
391 break;
392 }
393 return 0;
394 }
395
396 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
397 const nghttp2_frame* frame,
398 void* userData)
399 {
Ed Tanous62598e32023-07-17 17:06:25 -0700400 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800401 if (userData == nullptr)
402 {
Ed Tanous62598e32023-07-17 17:06:25 -0700403 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800404 return NGHTTP2_ERR_CALLBACK_FAILURE;
405 }
406 if (frame == nullptr)
407 {
Ed Tanous62598e32023-07-17 17:06:25 -0700408 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800409 return NGHTTP2_ERR_CALLBACK_FAILURE;
410 }
411 return userPtrToSelf(userData).onFrameRecvCallback(*frame);
412 }
413
414 static self_type& userPtrToSelf(void* userData)
415 {
416 // This method exists to keep the unsafe reinterpret cast in one
417 // place.
418 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
419 return *reinterpret_cast<self_type*>(userData);
420 }
421
422 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
423 int32_t streamId,
424 uint32_t /*unused*/, void* userData)
425 {
Ed Tanous62598e32023-07-17 17:06:25 -0700426 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800427 if (userData == nullptr)
428 {
Ed Tanous62598e32023-07-17 17:06:25 -0700429 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800430 return NGHTTP2_ERR_CALLBACK_FAILURE;
431 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700432 if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800433 {
434 return -1;
435 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800436 return 0;
437 }
438
439 int onHeaderCallback(const nghttp2_frame& frame,
440 std::span<const uint8_t> name,
441 std::span<const uint8_t> value)
442 {
443 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
444 std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
445 name.size());
446 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
447 std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
448 value.size());
449
Ed Tanous62598e32023-07-17 17:06:25 -0700450 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
451 valueSv);
Ed Tanousa07e9812024-03-19 10:31:13 -0700452 if (frame.hd.type != NGHTTP2_HEADERS)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800453 {
Ed Tanousa07e9812024-03-19 10:31:13 -0700454 return 0;
455 }
456 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
457 {
458 return 0;
459 }
460 auto thisStream = streams.find(frame.hd.stream_id);
461 if (thisStream == streams.end())
462 {
463 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
464 close();
465 return -1;
466 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800467
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700468 crow::Request& thisReq = *thisStream->second.req;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800469
Ed Tanousa07e9812024-03-19 10:31:13 -0700470 if (nameSv == ":path")
471 {
472 thisReq.target(valueSv);
473 }
474 else if (nameSv == ":method")
475 {
476 boost::beast::http::verb verb =
477 boost::beast::http::string_to_verb(valueSv);
478 if (verb == boost::beast::http::verb::unknown)
479 {
480 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
Ed Tanous50bfc912024-07-29 14:20:50 -0700481 verb = boost::beast::http::verb::trace;
Ed Tanousa07e9812024-03-19 10:31:13 -0700482 }
Myung Bae1873a042024-04-01 09:27:39 -0500483 thisReq.method(verb);
Ed Tanousa07e9812024-03-19 10:31:13 -0700484 }
485 else if (nameSv == ":scheme")
486 {
487 // Nothing to check on scheme
488 }
489 else
490 {
Myung Bae1873a042024-04-01 09:27:39 -0500491 thisReq.addHeader(nameSv, valueSv);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800492 }
493 return 0;
494 }
495
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400496 static int onHeaderCallbackStatic(
497 nghttp2_session* /* session */, const nghttp2_frame* frame,
498 const uint8_t* name, size_t namelen, const uint8_t* value,
499 size_t vallen, uint8_t /* flags */, void* userData)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800500 {
501 if (userData == nullptr)
502 {
Ed Tanous62598e32023-07-17 17:06:25 -0700503 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800504 return NGHTTP2_ERR_CALLBACK_FAILURE;
505 }
506 if (frame == nullptr)
507 {
Ed Tanous62598e32023-07-17 17:06:25 -0700508 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800509 return NGHTTP2_ERR_CALLBACK_FAILURE;
510 }
511 if (name == nullptr)
512 {
Ed Tanous62598e32023-07-17 17:06:25 -0700513 BMCWEB_LOG_CRITICAL("name was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800514 return NGHTTP2_ERR_CALLBACK_FAILURE;
515 }
516 if (value == nullptr)
517 {
Ed Tanous62598e32023-07-17 17:06:25 -0700518 BMCWEB_LOG_CRITICAL("value was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800519 return NGHTTP2_ERR_CALLBACK_FAILURE;
520 }
521 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
522 {value, vallen});
523 }
524
525 int onBeginHeadersCallback(const nghttp2_frame& frame)
526 {
527 if (frame.hd.type == NGHTTP2_HEADERS &&
528 frame.headers.cat == NGHTTP2_HCAT_REQUEST)
529 {
Ed Tanous62598e32023-07-17 17:06:25 -0700530 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800531
Ed Tanousb2539062024-03-12 16:58:35 -0700532 streams[frame.hd.stream_id];
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800533 }
534 return 0;
535 }
536
537 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
538 const nghttp2_frame* frame,
539 void* userData)
540 {
Ed Tanous62598e32023-07-17 17:06:25 -0700541 BMCWEB_LOG_DEBUG("on_begin_headers_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800542 if (userData == nullptr)
543 {
Ed Tanous62598e32023-07-17 17:06:25 -0700544 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800545 return NGHTTP2_ERR_CALLBACK_FAILURE;
546 }
547 if (frame == nullptr)
548 {
Ed Tanous62598e32023-07-17 17:06:25 -0700549 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800550 return NGHTTP2_ERR_CALLBACK_FAILURE;
551 }
552 return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
553 }
554
555 static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
556 const boost::system::error_code& ec,
557 size_t sendLength)
558 {
559 self->isWriting = false;
Ed Tanous62598e32023-07-17 17:06:25 -0700560 BMCWEB_LOG_DEBUG("Sent {}", sendLength);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800561 if (ec)
562 {
563 self->close();
564 return;
565 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800566 self->writeBuffer();
567 }
568
569 void writeBuffer()
570 {
571 if (isWriting)
572 {
573 return;
574 }
Ed Tanousd0882182024-01-26 23:45:25 -0800575 std::span<const uint8_t> data = ngSession.memSend();
576 if (data.empty())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800577 {
578 return;
579 }
580 isWriting = true;
Ed Tanousebe4c572025-02-08 14:29:53 -0800581 if (httpType == HttpType::HTTPS)
582 {
583 boost::asio::async_write(
584 adaptor, boost::asio::const_buffer(data.data(), data.size()),
585 std::bind_front(afterWriteBuffer, shared_from_this()));
586 }
587 else if (httpType == HttpType::HTTP)
588 {
589 boost::asio::async_write(
590 adaptor.next_layer(),
591 boost::asio::const_buffer(data.data(), data.size()),
592 std::bind_front(afterWriteBuffer, shared_from_this()));
593 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800594 }
595
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800596 void close()
597 {
Ed Tanousebe4c572025-02-08 14:29:53 -0800598 adaptor.next_layer().close();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800599 }
600
Ed Tanousd0882182024-01-26 23:45:25 -0800601 void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
602 const boost::system::error_code& ec,
603 size_t bytesTransferred)
604 {
605 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
606 bytesTransferred);
607
608 if (ec)
609 {
610 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
611 ec.message());
612 close();
613 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
614 return;
615 }
616 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
617
618 ssize_t readLen = ngSession.memRecv(bufferSpan);
619 if (readLen < 0)
620 {
621 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
622 close();
623 return;
624 }
625 writeBuffer();
626
627 doRead();
628 }
629
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800630 void doRead()
631 {
Ed Tanous62598e32023-07-17 17:06:25 -0700632 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
Ed Tanousebe4c572025-02-08 14:29:53 -0800633 if (httpType == HttpType::HTTPS)
634 {
635 adaptor.async_read_some(boost::asio::buffer(inBuffer),
636 std::bind_front(&self_type::afterDoRead,
637 this, shared_from_this()));
638 }
639 else if (httpType == HttpType::HTTP)
640 {
641 adaptor.next_layer().async_read_some(
642 boost::asio::buffer(inBuffer),
643 std::bind_front(&self_type::afterDoRead, this,
644 shared_from_this()));
645 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800646 }
647
648 // A mapping from http2 stream ID to Stream Data
Ed Tanous52e31622024-01-23 16:31:11 -0800649 std::map<int32_t, Http2StreamData> streams;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800650
Ed Tanousd0882182024-01-26 23:45:25 -0800651 std::array<uint8_t, 8192> inBuffer{};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800652
Ed Tanousebe4c572025-02-08 14:29:53 -0800653 HttpType httpType = HttpType::BOTH;
654 boost::asio::ssl::stream<Adaptor> adaptor;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800655 bool isWriting = false;
656
657 nghttp2_session ngSession;
658
659 Handler* handler;
660 std::function<std::string()>& getCachedDateStr;
661
Ed Tanous2e3cdf82025-08-01 09:49:35 -0700662 std::shared_ptr<persistent_data::UserSession> mtlsSession;
663
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800664 using std::enable_shared_from_this<
665 HTTP2Connection<Adaptor, Handler>>::shared_from_this;
666
667 using std::enable_shared_from_this<
668 HTTP2Connection<Adaptor, Handler>>::weak_from_this;
669};
670} // namespace crow