blob: 99604f53dad71960e74939c0624a2f55ffaf1219 [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>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080031
Ed Tanousd0882182024-01-26 23:45:25 -080032#include <array>
Ed Tanousd7857202025-01-28 15:32:26 -080033#include <bit>
34#include <cstddef>
35#include <cstdint>
Ed Tanousd0882182024-01-26 23:45:25 -080036#include <functional>
Ed Tanousd7857202025-01-28 15:32:26 -080037#include <map>
Ed Tanousd0882182024-01-26 23:45:25 -080038#include <memory>
Ed Tanousd7857202025-01-28 15:32:26 -080039#include <optional>
40#include <span>
Ed Tanous89cda632024-04-16 08:45:54 -070041#include <string>
Ed Tanousd7857202025-01-28 15:32:26 -080042#include <string_view>
Ed Tanous796ba932020-08-02 04:29:21 +000043#include <type_traits>
Ed Tanousd7857202025-01-28 15:32:26 -080044#include <utility>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080045#include <vector>
46
47namespace crow
48{
49
50struct Http2StreamData
51{
Jonathan Doman102a4cd2024-04-15 16:56:23 -070052 std::shared_ptr<Request> req = std::make_shared<Request>();
Ed Tanous325310d2024-03-15 09:05:04 -070053 std::optional<bmcweb::HttpBody::reader> reqReader;
Ed Tanous89cda632024-04-16 08:45:54 -070054 std::string accept;
Ed Tanous47f29342024-03-19 12:18:06 -070055 Response res;
Ed Tanousb2896142024-01-31 15:25:47 -080056 std::optional<bmcweb::HttpBody::writer> writer;
Ed Tanousfca2cbe2021-01-28 14:49:59 -080057};
58
59template <typename Adaptor, typename Handler>
60class HTTP2Connection :
61 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
62{
63 using self_type = HTTP2Connection<Adaptor, Handler>;
64
65 public:
Ed Tanousebe4c572025-02-08 14:29:53 -080066 HTTP2Connection(boost::asio::ssl::stream<Adaptor>&& adaptorIn,
67 Handler* handlerIn,
68 std::function<std::string()>& getCachedDateStrF,
69 HttpType httpTypeIn) :
70 httpType(httpTypeIn), adaptor(std::move(adaptorIn)),
71 ngSession(initializeNghttp2Session()), handler(handlerIn),
72 getCachedDateStr(getCachedDateStrF)
Ed Tanousfca2cbe2021-01-28 14:49:59 -080073 {}
74
75 void start()
76 {
77 // Create the control stream
Ed Tanousf42e8592023-08-25 10:47:44 -070078 streams[0];
Ed Tanousfca2cbe2021-01-28 14:49:59 -080079
80 if (sendServerConnectionHeader() != 0)
81 {
Ed Tanous62598e32023-07-17 17:06:25 -070082 BMCWEB_LOG_ERROR("send_server_connection_header failed");
Ed Tanousfca2cbe2021-01-28 14:49:59 -080083 return;
84 }
85 doRead();
86 }
87
Ed Tanouscd7dbb32025-02-01 12:37:56 -080088 void startFromSettings(std::string_view http2UpgradeSettings)
89 {
90 int ret = ngSession.sessionUpgrade2(http2UpgradeSettings,
91 false /*head_request*/);
92 if (ret != 0)
93 {
94 BMCWEB_LOG_ERROR("Failed to load upgrade header");
95 return;
96 }
97 // Create the control stream
98 streams[0];
99
100 if (sendServerConnectionHeader() != 0)
101 {
102 BMCWEB_LOG_ERROR("send_server_connection_header failed");
103 return;
104 }
105 doRead();
106 }
107
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800108 int sendServerConnectionHeader()
109 {
Ed Tanous62598e32023-07-17 17:06:25 -0700110 BMCWEB_LOG_DEBUG("send_server_connection_header()");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800111
112 uint32_t maxStreams = 4;
113 std::array<nghttp2_settings_entry, 2> iv = {
114 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
115 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
116 int rv = ngSession.submitSettings(iv);
117 if (rv != 0)
118 {
Ed Tanous62598e32023-07-17 17:06:25 -0700119 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800120 return -1;
121 }
Ed Tanousd0882182024-01-26 23:45:25 -0800122 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800123 return 0;
124 }
125
Patrick Williams504af5a2025-02-03 14:29:03 -0500126 static ssize_t fileReadCallback(
127 nghttp2_session* /* session */, int32_t streamId, uint8_t* buf,
128 size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/,
129 void* userPtr)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800130 {
Ed Tanousf42e8592023-08-25 10:47:44 -0700131 self_type& self = userPtrToSelf(userPtr);
132
133 auto streamIt = self.streams.find(streamId);
134 if (streamIt == self.streams.end())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800135 {
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800136 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
137 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700138 Http2StreamData& stream = streamIt->second;
Ed Tanous62598e32023-07-17 17:06:25 -0700139 BMCWEB_LOG_DEBUG("File read callback length: {}", length);
Ed Tanousd547d8d2024-03-16 18:04:41 -0700140 if (!stream.writer)
141 {
142 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
143 }
Ed Tanous52e31622024-01-23 16:31:11 -0800144 boost::beast::error_code ec;
145 boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
146 stream.writer->getWithMaxSize(ec, length);
147 if (ec)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700148 {
Ed Tanous325310d2024-03-15 09:05:04 -0700149 BMCWEB_LOG_CRITICAL("Failed to get buffer");
Ed Tanous27b0cf92023-08-07 12:02:40 -0700150 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
151 }
Ed Tanous52e31622024-01-23 16:31:11 -0800152 if (!out)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800153 {
Ed Tanous325310d2024-03-15 09:05:04 -0700154 BMCWEB_LOG_ERROR("Empty file, setting EOF");
Ed Tanous52e31622024-01-23 16:31:11 -0800155 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
156 return 0;
157 }
158
159 BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
160 if (length < out->first.size())
161 {
Ed Tanous325310d2024-03-15 09:05:04 -0700162 BMCWEB_LOG_CRITICAL(
163 "Buffer overflow that should never happen happened");
Ed Tanous52e31622024-01-23 16:31:11 -0800164 // Should never happen because of length limit on get() above
165 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
166 }
Ed Tanous325310d2024-03-15 09:05:04 -0700167 boost::asio::mutable_buffer writeableBuf(buf, length);
Ed Tanous52e31622024-01-23 16:31:11 -0800168 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
Ed Tanous325310d2024-03-15 09:05:04 -0700169 size_t copied = boost::asio::buffer_copy(writeableBuf, out->first);
170 if (copied != out->first.size())
171 {
172 BMCWEB_LOG_ERROR(
173 "Couldn't copy all {} bytes into buffer, only copied {}",
174 out->first.size(), copied);
175 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
176 }
Ed Tanous52e31622024-01-23 16:31:11 -0800177
178 if (!out->second)
179 {
Ed Tanous325310d2024-03-15 09:05:04 -0700180 BMCWEB_LOG_DEBUG("Setting EOF flag");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800181 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800182 }
Ed Tanous325310d2024-03-15 09:05:04 -0700183 return static_cast<ssize_t>(copied);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800184 }
185
186 nghttp2_nv headerFromStringViews(std::string_view name,
Ed Tanous52e31622024-01-23 16:31:11 -0800187 std::string_view value, uint8_t flags)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800188 {
189 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
190 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
Ed Tanous52e31622024-01-23 16:31:11 -0800191 return {nameData, valueData, name.size(), value.size(), flags};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800192 }
193
194 int sendResponse(Response& completedRes, int32_t streamId)
195 {
Ed Tanous62598e32023-07-17 17:06:25 -0700196 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800197
198 auto it = streams.find(streamId);
199 if (it == streams.end())
200 {
201 close();
202 return -1;
203 }
Ed Tanous499b5b42024-04-06 08:39:18 -0700204 Http2StreamData& stream = it->second;
205 Response& res = stream.res;
206 res = std::move(completedRes);
Ed Tanous499b5b42024-04-06 08:39:18 -0700207
Ed Tanous89cda632024-04-16 08:45:54 -0700208 completeResponseFields(stream.accept, res);
Ed Tanous499b5b42024-04-06 08:39:18 -0700209 res.addHeader(boost::beast::http::field::date, getCachedDateStr());
210 res.preparePayload();
211
212 boost::beast::http::fields& fields = res.fields();
213 std::string code = std::to_string(res.resultInt());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800214 std::vector<nghttp2_nv> hdr;
Ed Tanous52e31622024-01-23 16:31:11 -0800215 hdr.emplace_back(
216 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800217 for (const boost::beast::http::fields::value_type& header : fields)
218 {
Ed Tanous52e31622024-01-23 16:31:11 -0800219 hdr.emplace_back(headerFromStringViews(
Ed Tanousd0882182024-01-26 23:45:25 -0800220 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800221 }
Ed Tanousb2896142024-01-31 15:25:47 -0800222 http::response<bmcweb::HttpBody>& fbody = res.response;
Ed Tanous52e31622024-01-23 16:31:11 -0800223 stream.writer.emplace(fbody.base(), fbody.body());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800224
225 nghttp2_data_provider dataPrd{
Ed Tanousf42e8592023-08-25 10:47:44 -0700226 .source = {.fd = 0},
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800227 .read_callback = fileReadCallback,
228 };
229
230 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
231 if (rv != 0)
232 {
Ed Tanous62598e32023-07-17 17:06:25 -0700233 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800234 close();
235 return -1;
236 }
Ed Tanousd0882182024-01-26 23:45:25 -0800237 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800238
239 return 0;
240 }
241
242 nghttp2_session initializeNghttp2Session()
243 {
244 nghttp2_session_callbacks callbacks;
245 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
246 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
247 callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
248 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
Ed Tanous325310d2024-03-15 09:05:04 -0700249 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800250
251 nghttp2_session session(callbacks);
252 session.setUserData(this);
253
254 return session;
255 }
256
257 int onRequestRecv(int32_t streamId)
258 {
Ed Tanous62598e32023-07-17 17:06:25 -0700259 BMCWEB_LOG_DEBUG("on_request_recv");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800260
261 auto it = streams.find(streamId);
262 if (it == streams.end())
263 {
264 close();
265 return -1;
266 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700267 auto& reqReader = it->second.reqReader;
268 if (reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700269 {
270 boost::beast::error_code ec;
Ed Tanousdaadfb22024-12-20 09:25:54 -0800271 bmcweb::HttpBody::reader::finish(ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700272 if (ec)
273 {
274 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
275 close();
276 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
277 }
278 }
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700279 crow::Request& thisReq = *it->second.req;
Ed Tanous89cda632024-04-16 08:45:54 -0700280 it->second.accept = thisReq.getHeaderValue("Accept");
281
Ed Tanous62598e32023-07-17 17:06:25 -0700282 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
283 thisReq.url().encoded_path());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800284
Ed Tanousf42e8592023-08-25 10:47:44 -0700285 crow::Response& thisRes = it->second.res;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800286
287 thisRes.setCompleteRequestHandler(
288 [this, streamId](Response& completeRes) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400289 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
290 if (sendResponse(completeRes, streamId) != 0)
291 {
292 close();
293 return;
294 }
295 });
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800296 auto asyncResp =
Ed Tanousf42e8592023-08-25 10:47:44 -0700297 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
Ed Tanous83328312024-05-09 15:48:09 -0700298 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
Ed Tanous325310d2024-03-15 09:05:04 -0700299 {
Ed Tanous83328312024-05-09 15:48:09 -0700300 thisReq.session = crow::authentication::authenticate(
301 {}, asyncResp->res, thisReq.method(), thisReq.req, nullptr);
302 if (!crow::authentication::isOnAllowlist(thisReq.url().path(),
303 thisReq.method()) &&
304 thisReq.session == nullptr)
Ed Tanous499b5b42024-04-06 08:39:18 -0700305 {
Ed Tanous83328312024-05-09 15:48:09 -0700306 BMCWEB_LOG_WARNING("Authentication failed");
307 forward_unauthorized::sendUnauthorized(
308 thisReq.url().encoded_path(),
309 thisReq.getHeaderValue("X-Requested-With"),
310 thisReq.getHeaderValue("Accept"), asyncResp->res);
311 return 0;
Ed Tanous499b5b42024-04-06 08:39:18 -0700312 }
Ed Tanous325310d2024-03-15 09:05:04 -0700313 }
Ed Tanous83328312024-05-09 15:48:09 -0700314 std::string_view expected =
315 thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
316 BMCWEB_LOG_DEBUG("Setting expected hash {}", expected);
317 if (!expected.empty())
318 {
319 asyncResp->res.setExpectedHash(expected);
320 }
321 handler->handle(it->second.req, asyncResp);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800322 return 0;
323 }
324
Ed Tanous325310d2024-03-15 09:05:04 -0700325 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
326 const uint8_t* data, size_t len)
327 {
328 auto thisStream = streams.find(streamId);
329 if (thisStream == streams.end())
330 {
331 BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
332 close();
333 return -1;
334 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700335
336 std::optional<bmcweb::HttpBody::reader>& reqReader =
337 thisStream->second.reqReader;
338 if (!reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700339 {
Ed Tanous8e5cc7b2024-04-02 11:00:54 -0700340 reqReader.emplace(
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700341 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
342 thisStream->second.req->req.body()));
Ed Tanous325310d2024-03-15 09:05:04 -0700343 }
344 boost::beast::error_code ec;
Ed Tanousd547d8d2024-03-16 18:04:41 -0700345 reqReader->put(boost::asio::const_buffer(data, len), ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700346 if (ec)
347 {
348 BMCWEB_LOG_CRITICAL("Failed to write payload");
349 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
350 }
351 return 0;
352 }
353
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400354 static int onDataChunkRecvStatic(
355 nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
356 const uint8_t* data, size_t len, void* userData)
Ed Tanous325310d2024-03-15 09:05:04 -0700357 {
358 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
359 if (userData == nullptr)
360 {
361 BMCWEB_LOG_CRITICAL("user data was null?");
362 return NGHTTP2_ERR_CALLBACK_FAILURE;
363 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400364 return userPtrToSelf(userData).onDataChunkRecvCallback(
365 flags, streamId, data, len);
Ed Tanous325310d2024-03-15 09:05:04 -0700366 }
367
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800368 int onFrameRecvCallback(const nghttp2_frame& frame)
369 {
Ed Tanous62598e32023-07-17 17:06:25 -0700370 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800371 switch (frame.hd.type)
372 {
373 case NGHTTP2_DATA:
374 case NGHTTP2_HEADERS:
375 // Check that the client request has finished
376 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
377 {
378 return onRequestRecv(frame.hd.stream_id);
379 }
380 break;
381 default:
382 break;
383 }
384 return 0;
385 }
386
387 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
388 const nghttp2_frame* frame,
389 void* userData)
390 {
Ed Tanous62598e32023-07-17 17:06:25 -0700391 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800392 if (userData == nullptr)
393 {
Ed Tanous62598e32023-07-17 17:06:25 -0700394 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800395 return NGHTTP2_ERR_CALLBACK_FAILURE;
396 }
397 if (frame == nullptr)
398 {
Ed Tanous62598e32023-07-17 17:06:25 -0700399 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800400 return NGHTTP2_ERR_CALLBACK_FAILURE;
401 }
402 return userPtrToSelf(userData).onFrameRecvCallback(*frame);
403 }
404
405 static self_type& userPtrToSelf(void* userData)
406 {
407 // This method exists to keep the unsafe reinterpret cast in one
408 // place.
409 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
410 return *reinterpret_cast<self_type*>(userData);
411 }
412
413 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
414 int32_t streamId,
415 uint32_t /*unused*/, void* userData)
416 {
Ed Tanous62598e32023-07-17 17:06:25 -0700417 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800418 if (userData == nullptr)
419 {
Ed Tanous62598e32023-07-17 17:06:25 -0700420 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800421 return NGHTTP2_ERR_CALLBACK_FAILURE;
422 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700423 if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800424 {
425 return -1;
426 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800427 return 0;
428 }
429
430 int onHeaderCallback(const nghttp2_frame& frame,
431 std::span<const uint8_t> name,
432 std::span<const uint8_t> value)
433 {
434 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
435 std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
436 name.size());
437 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
438 std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
439 value.size());
440
Ed Tanous62598e32023-07-17 17:06:25 -0700441 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
442 valueSv);
Ed Tanousa07e9812024-03-19 10:31:13 -0700443 if (frame.hd.type != NGHTTP2_HEADERS)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800444 {
Ed Tanousa07e9812024-03-19 10:31:13 -0700445 return 0;
446 }
447 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
448 {
449 return 0;
450 }
451 auto thisStream = streams.find(frame.hd.stream_id);
452 if (thisStream == streams.end())
453 {
454 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
455 close();
456 return -1;
457 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800458
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700459 crow::Request& thisReq = *thisStream->second.req;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800460
Ed Tanousa07e9812024-03-19 10:31:13 -0700461 if (nameSv == ":path")
462 {
463 thisReq.target(valueSv);
464 }
465 else if (nameSv == ":method")
466 {
467 boost::beast::http::verb verb =
468 boost::beast::http::string_to_verb(valueSv);
469 if (verb == boost::beast::http::verb::unknown)
470 {
471 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
Ed Tanous50bfc912024-07-29 14:20:50 -0700472 verb = boost::beast::http::verb::trace;
Ed Tanousa07e9812024-03-19 10:31:13 -0700473 }
Myung Bae1873a042024-04-01 09:27:39 -0500474 thisReq.method(verb);
Ed Tanousa07e9812024-03-19 10:31:13 -0700475 }
476 else if (nameSv == ":scheme")
477 {
478 // Nothing to check on scheme
479 }
480 else
481 {
Myung Bae1873a042024-04-01 09:27:39 -0500482 thisReq.addHeader(nameSv, valueSv);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800483 }
484 return 0;
485 }
486
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400487 static int onHeaderCallbackStatic(
488 nghttp2_session* /* session */, const nghttp2_frame* frame,
489 const uint8_t* name, size_t namelen, const uint8_t* value,
490 size_t vallen, uint8_t /* flags */, void* userData)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800491 {
492 if (userData == nullptr)
493 {
Ed Tanous62598e32023-07-17 17:06:25 -0700494 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800495 return NGHTTP2_ERR_CALLBACK_FAILURE;
496 }
497 if (frame == nullptr)
498 {
Ed Tanous62598e32023-07-17 17:06:25 -0700499 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800500 return NGHTTP2_ERR_CALLBACK_FAILURE;
501 }
502 if (name == nullptr)
503 {
Ed Tanous62598e32023-07-17 17:06:25 -0700504 BMCWEB_LOG_CRITICAL("name was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800505 return NGHTTP2_ERR_CALLBACK_FAILURE;
506 }
507 if (value == nullptr)
508 {
Ed Tanous62598e32023-07-17 17:06:25 -0700509 BMCWEB_LOG_CRITICAL("value was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800510 return NGHTTP2_ERR_CALLBACK_FAILURE;
511 }
512 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
513 {value, vallen});
514 }
515
516 int onBeginHeadersCallback(const nghttp2_frame& frame)
517 {
518 if (frame.hd.type == NGHTTP2_HEADERS &&
519 frame.headers.cat == NGHTTP2_HCAT_REQUEST)
520 {
Ed Tanous62598e32023-07-17 17:06:25 -0700521 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800522
Ed Tanous3a499412024-06-05 16:05:35 -0700523 streams.emplace(frame.hd.stream_id, Http2StreamData());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800524 }
525 return 0;
526 }
527
528 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
529 const nghttp2_frame* frame,
530 void* userData)
531 {
Ed Tanous62598e32023-07-17 17:06:25 -0700532 BMCWEB_LOG_DEBUG("on_begin_headers_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800533 if (userData == nullptr)
534 {
Ed Tanous62598e32023-07-17 17:06:25 -0700535 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800536 return NGHTTP2_ERR_CALLBACK_FAILURE;
537 }
538 if (frame == nullptr)
539 {
Ed Tanous62598e32023-07-17 17:06:25 -0700540 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800541 return NGHTTP2_ERR_CALLBACK_FAILURE;
542 }
543 return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
544 }
545
546 static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
547 const boost::system::error_code& ec,
548 size_t sendLength)
549 {
550 self->isWriting = false;
Ed Tanous62598e32023-07-17 17:06:25 -0700551 BMCWEB_LOG_DEBUG("Sent {}", sendLength);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800552 if (ec)
553 {
554 self->close();
555 return;
556 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800557 self->writeBuffer();
558 }
559
560 void writeBuffer()
561 {
562 if (isWriting)
563 {
564 return;
565 }
Ed Tanousd0882182024-01-26 23:45:25 -0800566 std::span<const uint8_t> data = ngSession.memSend();
567 if (data.empty())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800568 {
569 return;
570 }
571 isWriting = true;
Ed Tanousebe4c572025-02-08 14:29:53 -0800572 if (httpType == HttpType::HTTPS)
573 {
574 boost::asio::async_write(
575 adaptor, boost::asio::const_buffer(data.data(), data.size()),
576 std::bind_front(afterWriteBuffer, shared_from_this()));
577 }
578 else if (httpType == HttpType::HTTP)
579 {
580 boost::asio::async_write(
581 adaptor.next_layer(),
582 boost::asio::const_buffer(data.data(), data.size()),
583 std::bind_front(afterWriteBuffer, shared_from_this()));
584 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800585 }
586
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800587 void close()
588 {
Ed Tanousebe4c572025-02-08 14:29:53 -0800589 adaptor.next_layer().close();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800590 }
591
Ed Tanousd0882182024-01-26 23:45:25 -0800592 void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
593 const boost::system::error_code& ec,
594 size_t bytesTransferred)
595 {
596 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
597 bytesTransferred);
598
599 if (ec)
600 {
601 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
602 ec.message());
603 close();
604 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
605 return;
606 }
607 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
608
609 ssize_t readLen = ngSession.memRecv(bufferSpan);
610 if (readLen < 0)
611 {
612 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
613 close();
614 return;
615 }
616 writeBuffer();
617
618 doRead();
619 }
620
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800621 void doRead()
622 {
Ed Tanous62598e32023-07-17 17:06:25 -0700623 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
Ed Tanousebe4c572025-02-08 14:29:53 -0800624 if (httpType == HttpType::HTTPS)
625 {
626 adaptor.async_read_some(boost::asio::buffer(inBuffer),
627 std::bind_front(&self_type::afterDoRead,
628 this, shared_from_this()));
629 }
630 else if (httpType == HttpType::HTTP)
631 {
632 adaptor.next_layer().async_read_some(
633 boost::asio::buffer(inBuffer),
634 std::bind_front(&self_type::afterDoRead, this,
635 shared_from_this()));
636 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800637 }
638
639 // A mapping from http2 stream ID to Stream Data
Ed Tanous52e31622024-01-23 16:31:11 -0800640 std::map<int32_t, Http2StreamData> streams;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800641
Ed Tanousd0882182024-01-26 23:45:25 -0800642 std::array<uint8_t, 8192> inBuffer{};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800643
Ed Tanousebe4c572025-02-08 14:29:53 -0800644 HttpType httpType = HttpType::BOTH;
645 boost::asio::ssl::stream<Adaptor> adaptor;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800646 bool isWriting = false;
647
648 nghttp2_session ngSession;
649
650 Handler* handler;
651 std::function<std::string()>& getCachedDateStr;
652
653 using std::enable_shared_from_this<
654 HTTP2Connection<Adaptor, Handler>>::shared_from_this;
655
656 using std::enable_shared_from_this<
657 HTTP2Connection<Adaptor, Handler>>::weak_from_this;
658};
659} // namespace crow