blob: 7b90ff3d14efc16141b90d491029ee0fdb71b077 [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;
Ed Tanousd07a5ee2025-09-25 07:53:24 -0700116
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 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800133 int rv = ngSession.submitSettings(iv);
134 if (rv != 0)
135 {
Ed Tanous62598e32023-07-17 17:06:25 -0700136 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800137 return -1;
138 }
Ed Tanousd0882182024-01-26 23:45:25 -0800139 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800140 return 0;
141 }
142
Patrick Williams504af5a2025-02-03 14:29:03 -0500143 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)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800147 {
Ed Tanousf42e8592023-08-25 10:47:44 -0700148 self_type& self = userPtrToSelf(userPtr);
149
150 auto streamIt = self.streams.find(streamId);
151 if (streamIt == self.streams.end())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800152 {
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800153 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
154 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700155 Http2StreamData& stream = streamIt->second;
Ed Tanous62598e32023-07-17 17:06:25 -0700156 BMCWEB_LOG_DEBUG("File read callback length: {}", length);
Ed Tanousd547d8d2024-03-16 18:04:41 -0700157 if (!stream.writer)
158 {
159 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
160 }
Ed Tanous52e31622024-01-23 16:31:11 -0800161 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)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700165 {
Ed Tanous325310d2024-03-15 09:05:04 -0700166 BMCWEB_LOG_CRITICAL("Failed to get buffer");
Ed Tanous27b0cf92023-08-07 12:02:40 -0700167 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
168 }
Ed Tanous52e31622024-01-23 16:31:11 -0800169 if (!out)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800170 {
Ed Tanous325310d2024-03-15 09:05:04 -0700171 BMCWEB_LOG_ERROR("Empty file, setting EOF");
Ed Tanous52e31622024-01-23 16:31:11 -0800172 *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 {
Ed Tanous325310d2024-03-15 09:05:04 -0700179 BMCWEB_LOG_CRITICAL(
180 "Buffer overflow that should never happen happened");
Ed Tanous52e31622024-01-23 16:31:11 -0800181 // Should never happen because of length limit on get() above
182 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
183 }
Ed Tanous325310d2024-03-15 09:05:04 -0700184 boost::asio::mutable_buffer writeableBuf(buf, length);
Ed Tanous52e31622024-01-23 16:31:11 -0800185 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
Ed Tanous325310d2024-03-15 09:05:04 -0700186 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 }
Ed Tanous52e31622024-01-23 16:31:11 -0800194
195 if (!out->second)
196 {
Ed Tanous325310d2024-03-15 09:05:04 -0700197 BMCWEB_LOG_DEBUG("Setting EOF flag");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800198 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800199 }
Ed Tanous325310d2024-03-15 09:05:04 -0700200 return static_cast<ssize_t>(copied);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800201 }
202
203 nghttp2_nv headerFromStringViews(std::string_view name,
Ed Tanous52e31622024-01-23 16:31:11 -0800204 std::string_view value, uint8_t flags)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800205 {
206 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
207 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
Ed Tanous52e31622024-01-23 16:31:11 -0800208 return {nameData, valueData, name.size(), value.size(), flags};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800209 }
210
211 int sendResponse(Response& completedRes, int32_t streamId)
212 {
Ed Tanous62598e32023-07-17 17:06:25 -0700213 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800214
215 auto it = streams.find(streamId);
216 if (it == streams.end())
217 {
218 close();
219 return -1;
220 }
Ed Tanous499b5b42024-04-06 08:39:18 -0700221 Http2StreamData& stream = it->second;
222 Response& res = stream.res;
223 res = std::move(completedRes);
Ed Tanous499b5b42024-04-06 08:39:18 -0700224
Ed Tanousb2539062024-03-12 16:58:35 -0700225 completeResponseFields(stream.accept, stream.acceptEnc, res);
Ed Tanous499b5b42024-04-06 08:39:18 -0700226 res.addHeader(boost::beast::http::field::date, getCachedDateStr());
Abiola Asojod23d6342025-06-18 20:15:24 +0000227 boost::urls::url_view urlView;
228 if (stream.req != nullptr)
229 {
230 urlView = stream.req->url();
231 }
232 res.preparePayload(urlView);
Ed Tanous499b5b42024-04-06 08:39:18 -0700233
234 boost::beast::http::fields& fields = res.fields();
235 std::string code = std::to_string(res.resultInt());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800236 std::vector<nghttp2_nv> hdr;
Ed Tanous52e31622024-01-23 16:31:11 -0800237 hdr.emplace_back(
238 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800239 for (const boost::beast::http::fields::value_type& header : fields)
240 {
Ed Tanous52e31622024-01-23 16:31:11 -0800241 hdr.emplace_back(headerFromStringViews(
Ed Tanousd0882182024-01-26 23:45:25 -0800242 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800243 }
Ed Tanousb2896142024-01-31 15:25:47 -0800244 http::response<bmcweb::HttpBody>& fbody = res.response;
Ed Tanous52e31622024-01-23 16:31:11 -0800245 stream.writer.emplace(fbody.base(), fbody.body());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800246
247 nghttp2_data_provider dataPrd{
Ed Tanousf42e8592023-08-25 10:47:44 -0700248 .source = {.fd = 0},
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800249 .read_callback = fileReadCallback,
250 };
251
252 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
253 if (rv != 0)
254 {
Ed Tanous62598e32023-07-17 17:06:25 -0700255 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800256 close();
257 return -1;
258 }
Ed Tanousd0882182024-01-26 23:45:25 -0800259 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800260
261 return 0;
262 }
263
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);
Ed Tanous325310d2024-03-15 09:05:04 -0700271 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800272
273 nghttp2_session session(callbacks);
274 session.setUserData(this);
275
276 return session;
277 }
278
279 int onRequestRecv(int32_t streamId)
280 {
Ed Tanous62598e32023-07-17 17:06:25 -0700281 BMCWEB_LOG_DEBUG("on_request_recv");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800282
283 auto it = streams.find(streamId);
284 if (it == streams.end())
285 {
286 close();
287 return -1;
288 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700289 auto& reqReader = it->second.reqReader;
290 if (reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700291 {
292 boost::beast::error_code ec;
Ed Tanousdaadfb22024-12-20 09:25:54 -0800293 bmcweb::HttpBody::reader::finish(ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700294 if (ec)
295 {
296 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
297 close();
298 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
299 }
300 }
Ed Tanous05c27352024-10-09 17:00:37 -0700301 Request& thisReq = *it->second.req;
Ed Tanousb2539062024-03-12 16:58:35 -0700302 using boost::beast::http::field;
303 it->second.accept = thisReq.getHeaderValue(field::accept);
304 it->second.acceptEnc = thisReq.getHeaderValue(field::accept_encoding);
Ed Tanous89cda632024-04-16 08:45:54 -0700305
Ed Tanous62598e32023-07-17 17:06:25 -0700306 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
307 thisReq.url().encoded_path());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800308
Ed Tanous05c27352024-10-09 17:00:37 -0700309 Response& thisRes = it->second.res;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800310
311 thisRes.setCompleteRequestHandler(
312 [this, streamId](Response& completeRes) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400313 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
314 if (sendResponse(completeRes, streamId) != 0)
315 {
316 close();
317 return;
318 }
319 });
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800320 auto asyncResp =
Ed Tanousf42e8592023-08-25 10:47:44 -0700321 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
Ed Tanous83328312024-05-09 15:48:09 -0700322 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
Ed Tanous325310d2024-03-15 09:05:04 -0700323 {
Ed Tanous05c27352024-10-09 17:00:37 -0700324 thisReq.session = authentication::authenticate(
Ed Tanous2e3cdf82025-08-01 09:49:35 -0700325 {}, asyncResp->res, thisReq.method(), thisReq.req, mtlsSession);
Ed Tanous05c27352024-10-09 17:00:37 -0700326 if (!authentication::isOnAllowlist(thisReq.url().path(),
327 thisReq.method()) &&
Ed Tanous83328312024-05-09 15:48:09 -0700328 thisReq.session == nullptr)
Ed Tanous499b5b42024-04-06 08:39:18 -0700329 {
Ed Tanous83328312024-05-09 15:48:09 -0700330 BMCWEB_LOG_WARNING("Authentication failed");
331 forward_unauthorized::sendUnauthorized(
332 thisReq.url().encoded_path(),
333 thisReq.getHeaderValue("X-Requested-With"),
334 thisReq.getHeaderValue("Accept"), asyncResp->res);
335 return 0;
Ed Tanous499b5b42024-04-06 08:39:18 -0700336 }
Ed Tanous325310d2024-03-15 09:05:04 -0700337 }
Corey Ethington08fad5d2025-07-31 12:14:27 -0400338 std::string_view expectedEtag =
Ed Tanous83328312024-05-09 15:48:09 -0700339 thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
Corey Ethington08fad5d2025-07-31 12:14:27 -0400340 BMCWEB_LOG_DEBUG("Setting expected etag {}", expectedEtag);
341 if (!expectedEtag.empty())
Ed Tanous83328312024-05-09 15:48:09 -0700342 {
Corey Ethington08fad5d2025-07-31 12:14:27 -0400343 asyncResp->res.setExpectedEtag(expectedEtag);
Ed Tanous83328312024-05-09 15:48:09 -0700344 }
345 handler->handle(it->second.req, asyncResp);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800346 return 0;
347 }
348
Ed Tanous325310d2024-03-15 09:05:04 -0700349 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
350 const uint8_t* data, size_t len)
351 {
352 auto thisStream = streams.find(streamId);
353 if (thisStream == streams.end())
354 {
355 BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
356 close();
357 return -1;
358 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700359
360 std::optional<bmcweb::HttpBody::reader>& reqReader =
361 thisStream->second.reqReader;
362 if (!reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700363 {
Ed Tanous8e5cc7b2024-04-02 11:00:54 -0700364 reqReader.emplace(
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700365 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
366 thisStream->second.req->req.body()));
Ed Tanous325310d2024-03-15 09:05:04 -0700367 }
368 boost::beast::error_code ec;
Ed Tanousd547d8d2024-03-16 18:04:41 -0700369 reqReader->put(boost::asio::const_buffer(data, len), ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700370 if (ec)
371 {
372 BMCWEB_LOG_CRITICAL("Failed to write payload");
373 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
374 }
375 return 0;
376 }
377
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400378 static int onDataChunkRecvStatic(
379 nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
380 const uint8_t* data, size_t len, void* userData)
Ed Tanous325310d2024-03-15 09:05:04 -0700381 {
Ed Tanousd07a5ee2025-09-25 07:53:24 -0700382 BMCWEB_LOG_DEBUG("onDataChunkRecvStatic");
Ed Tanous325310d2024-03-15 09:05:04 -0700383 if (userData == nullptr)
384 {
385 BMCWEB_LOG_CRITICAL("user data was null?");
386 return NGHTTP2_ERR_CALLBACK_FAILURE;
387 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400388 return userPtrToSelf(userData).onDataChunkRecvCallback(
389 flags, streamId, data, len);
Ed Tanous325310d2024-03-15 09:05:04 -0700390 }
391
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800392 int onFrameRecvCallback(const nghttp2_frame& frame)
393 {
Ed Tanous62598e32023-07-17 17:06:25 -0700394 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800395 switch (frame.hd.type)
396 {
397 case NGHTTP2_DATA:
398 case NGHTTP2_HEADERS:
399 // Check that the client request has finished
400 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
401 {
402 return onRequestRecv(frame.hd.stream_id);
403 }
404 break;
405 default:
406 break;
407 }
408 return 0;
409 }
410
411 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
412 const nghttp2_frame* frame,
413 void* userData)
414 {
Ed Tanousd07a5ee2025-09-25 07:53:24 -0700415 BMCWEB_LOG_DEBUG("on_frame_recv_callback. Frame type {}",
416 static_cast<int>(frame->hd.type));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800417 if (userData == nullptr)
418 {
Ed Tanous62598e32023-07-17 17:06:25 -0700419 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800420 return NGHTTP2_ERR_CALLBACK_FAILURE;
421 }
422 if (frame == nullptr)
423 {
Ed Tanous62598e32023-07-17 17:06:25 -0700424 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800425 return NGHTTP2_ERR_CALLBACK_FAILURE;
426 }
427 return userPtrToSelf(userData).onFrameRecvCallback(*frame);
428 }
429
430 static self_type& userPtrToSelf(void* userData)
431 {
432 // This method exists to keep the unsafe reinterpret cast in one
433 // place.
434 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
435 return *reinterpret_cast<self_type*>(userData);
436 }
437
438 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
439 int32_t streamId,
440 uint32_t /*unused*/, void* userData)
441 {
Ed Tanous62598e32023-07-17 17:06:25 -0700442 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800443 if (userData == nullptr)
444 {
Ed Tanous62598e32023-07-17 17:06:25 -0700445 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800446 return NGHTTP2_ERR_CALLBACK_FAILURE;
447 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700448 if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800449 {
450 return -1;
451 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800452 return 0;
453 }
454
455 int onHeaderCallback(const nghttp2_frame& frame,
456 std::span<const uint8_t> name,
457 std::span<const uint8_t> value)
458 {
459 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
460 std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
461 name.size());
462 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
463 std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
464 value.size());
465
Ed Tanous62598e32023-07-17 17:06:25 -0700466 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
467 valueSv);
Ed Tanousa07e9812024-03-19 10:31:13 -0700468 if (frame.hd.type != NGHTTP2_HEADERS)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800469 {
Ed Tanousa07e9812024-03-19 10:31:13 -0700470 return 0;
471 }
472 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
473 {
474 return 0;
475 }
476 auto thisStream = streams.find(frame.hd.stream_id);
477 if (thisStream == streams.end())
478 {
479 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
480 close();
481 return -1;
482 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800483
Ed Tanous05c27352024-10-09 17:00:37 -0700484 Request& thisReq = *thisStream->second.req;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800485
Ed Tanousa07e9812024-03-19 10:31:13 -0700486 if (nameSv == ":path")
487 {
488 thisReq.target(valueSv);
489 }
490 else if (nameSv == ":method")
491 {
492 boost::beast::http::verb verb =
493 boost::beast::http::string_to_verb(valueSv);
494 if (verb == boost::beast::http::verb::unknown)
495 {
496 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
Ed Tanous50bfc912024-07-29 14:20:50 -0700497 verb = boost::beast::http::verb::trace;
Ed Tanousa07e9812024-03-19 10:31:13 -0700498 }
Myung Bae1873a042024-04-01 09:27:39 -0500499 thisReq.method(verb);
Ed Tanousa07e9812024-03-19 10:31:13 -0700500 }
Ed Tanousdd859f92025-09-23 16:16:14 +0530501 else if (nameSv.starts_with(":"))
Ed Tanousa07e9812024-03-19 10:31:13 -0700502 {
Ed Tanousdd859f92025-09-23 16:16:14 +0530503 // Ignore all other http2 headers
504 // :scheme and :authority are other valid http2 fields that might
505 // show up here.
Ed Tanousa07e9812024-03-19 10:31:13 -0700506 }
507 else
508 {
Myung Bae1873a042024-04-01 09:27:39 -0500509 thisReq.addHeader(nameSv, valueSv);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800510 }
511 return 0;
512 }
513
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400514 static int onHeaderCallbackStatic(
515 nghttp2_session* /* session */, const nghttp2_frame* frame,
516 const uint8_t* name, size_t namelen, const uint8_t* value,
517 size_t vallen, uint8_t /* flags */, void* userData)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800518 {
519 if (userData == nullptr)
520 {
Ed Tanous62598e32023-07-17 17:06:25 -0700521 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800522 return NGHTTP2_ERR_CALLBACK_FAILURE;
523 }
524 if (frame == nullptr)
525 {
Ed Tanous62598e32023-07-17 17:06:25 -0700526 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800527 return NGHTTP2_ERR_CALLBACK_FAILURE;
528 }
529 if (name == nullptr)
530 {
Ed Tanous62598e32023-07-17 17:06:25 -0700531 BMCWEB_LOG_CRITICAL("name was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800532 return NGHTTP2_ERR_CALLBACK_FAILURE;
533 }
534 if (value == nullptr)
535 {
Ed Tanous62598e32023-07-17 17:06:25 -0700536 BMCWEB_LOG_CRITICAL("value was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800537 return NGHTTP2_ERR_CALLBACK_FAILURE;
538 }
539 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
540 {value, vallen});
541 }
542
543 int onBeginHeadersCallback(const nghttp2_frame& frame)
544 {
545 if (frame.hd.type == NGHTTP2_HEADERS &&
546 frame.headers.cat == NGHTTP2_HCAT_REQUEST)
547 {
Ed Tanous62598e32023-07-17 17:06:25 -0700548 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800549
Ed Tanousb2539062024-03-12 16:58:35 -0700550 streams[frame.hd.stream_id];
Ed Tanousd07a5ee2025-09-25 07:53:24 -0700551 if (ngSession.setLocalWindowSize(
552 NGHTTP2_FLAG_NONE, frame.hd.stream_id, 16384 * 32) != 0)
553 {
554 BMCWEB_LOG_ERROR("Failed to set local window size");
555 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800556 }
557 return 0;
558 }
559
560 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
561 const nghttp2_frame* frame,
562 void* userData)
563 {
Ed Tanous62598e32023-07-17 17:06:25 -0700564 BMCWEB_LOG_DEBUG("on_begin_headers_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800565 if (userData == nullptr)
566 {
Ed Tanous62598e32023-07-17 17:06:25 -0700567 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800568 return NGHTTP2_ERR_CALLBACK_FAILURE;
569 }
570 if (frame == nullptr)
571 {
Ed Tanous62598e32023-07-17 17:06:25 -0700572 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800573 return NGHTTP2_ERR_CALLBACK_FAILURE;
574 }
575 return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
576 }
577
578 static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
579 const boost::system::error_code& ec,
580 size_t sendLength)
581 {
582 self->isWriting = false;
Ed Tanous62598e32023-07-17 17:06:25 -0700583 BMCWEB_LOG_DEBUG("Sent {}", sendLength);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800584 if (ec)
585 {
586 self->close();
587 return;
588 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800589 self->writeBuffer();
590 }
591
592 void writeBuffer()
593 {
594 if (isWriting)
595 {
596 return;
597 }
Ed Tanousd0882182024-01-26 23:45:25 -0800598 std::span<const uint8_t> data = ngSession.memSend();
599 if (data.empty())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800600 {
601 return;
602 }
603 isWriting = true;
Ed Tanousebe4c572025-02-08 14:29:53 -0800604 if (httpType == HttpType::HTTPS)
605 {
606 boost::asio::async_write(
607 adaptor, boost::asio::const_buffer(data.data(), data.size()),
608 std::bind_front(afterWriteBuffer, shared_from_this()));
609 }
610 else if (httpType == HttpType::HTTP)
611 {
612 boost::asio::async_write(
613 adaptor.next_layer(),
614 boost::asio::const_buffer(data.data(), data.size()),
615 std::bind_front(afterWriteBuffer, shared_from_this()));
616 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800617 }
618
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800619 void close()
620 {
Ed Tanousebe4c572025-02-08 14:29:53 -0800621 adaptor.next_layer().close();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800622 }
623
Ed Tanousd0882182024-01-26 23:45:25 -0800624 void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
625 const boost::system::error_code& ec,
626 size_t bytesTransferred)
627 {
628 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
629 bytesTransferred);
630
631 if (ec)
632 {
633 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
634 ec.message());
635 close();
636 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
637 return;
638 }
639 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
640
641 ssize_t readLen = ngSession.memRecv(bufferSpan);
642 if (readLen < 0)
643 {
644 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
645 close();
646 return;
647 }
648 writeBuffer();
649
650 doRead();
651 }
652
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800653 void doRead()
654 {
Ed Tanous62598e32023-07-17 17:06:25 -0700655 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
Ed Tanousebe4c572025-02-08 14:29:53 -0800656 if (httpType == HttpType::HTTPS)
657 {
658 adaptor.async_read_some(boost::asio::buffer(inBuffer),
659 std::bind_front(&self_type::afterDoRead,
660 this, shared_from_this()));
661 }
662 else if (httpType == HttpType::HTTP)
663 {
664 adaptor.next_layer().async_read_some(
665 boost::asio::buffer(inBuffer),
666 std::bind_front(&self_type::afterDoRead, this,
667 shared_from_this()));
668 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800669 }
670
671 // A mapping from http2 stream ID to Stream Data
Ed Tanous52e31622024-01-23 16:31:11 -0800672 std::map<int32_t, Http2StreamData> streams;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800673
Ed Tanousd0882182024-01-26 23:45:25 -0800674 std::array<uint8_t, 8192> inBuffer{};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800675
Ed Tanousebe4c572025-02-08 14:29:53 -0800676 HttpType httpType = HttpType::BOTH;
677 boost::asio::ssl::stream<Adaptor> adaptor;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800678 bool isWriting = false;
679
680 nghttp2_session ngSession;
681
682 Handler* handler;
683 std::function<std::string()>& getCachedDateStr;
684
Ed Tanous2e3cdf82025-08-01 09:49:35 -0700685 std::shared_ptr<persistent_data::UserSession> mtlsSession;
686
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800687 using std::enable_shared_from_this<
688 HTTP2Connection<Adaptor, Handler>>::shared_from_this;
689
690 using std::enable_shared_from_this<
691 HTTP2Connection<Adaptor, Handler>>::weak_from_this;
692};
693} // namespace crow