blob: e9e9cd816c8e7ae113583afbab079403d1b09294 [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 Tanousb2539062024-03-12 16:58:35 -070055 std::string acceptEnc;
Ed Tanous47f29342024-03-19 12:18:06 -070056 Response res;
Ed Tanousb2896142024-01-31 15:25:47 -080057 std::optional<bmcweb::HttpBody::writer> writer;
Ed Tanousfca2cbe2021-01-28 14:49:59 -080058};
59
60template <typename Adaptor, typename Handler>
61class HTTP2Connection :
62 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
63{
64 using self_type = HTTP2Connection<Adaptor, Handler>;
65
66 public:
Ed Tanousebe4c572025-02-08 14:29:53 -080067 HTTP2Connection(boost::asio::ssl::stream<Adaptor>&& adaptorIn,
68 Handler* handlerIn,
69 std::function<std::string()>& getCachedDateStrF,
70 HttpType httpTypeIn) :
71 httpType(httpTypeIn), adaptor(std::move(adaptorIn)),
72 ngSession(initializeNghttp2Session()), handler(handlerIn),
73 getCachedDateStr(getCachedDateStrF)
Ed Tanousfca2cbe2021-01-28 14:49:59 -080074 {}
75
76 void start()
77 {
78 // Create the control stream
Ed Tanousf42e8592023-08-25 10:47:44 -070079 streams[0];
Ed Tanousfca2cbe2021-01-28 14:49:59 -080080
81 if (sendServerConnectionHeader() != 0)
82 {
Ed Tanous62598e32023-07-17 17:06:25 -070083 BMCWEB_LOG_ERROR("send_server_connection_header failed");
Ed Tanousfca2cbe2021-01-28 14:49:59 -080084 return;
85 }
86 doRead();
87 }
88
Ed Tanouscd7dbb32025-02-01 12:37:56 -080089 void startFromSettings(std::string_view http2UpgradeSettings)
90 {
91 int ret = ngSession.sessionUpgrade2(http2UpgradeSettings,
92 false /*head_request*/);
93 if (ret != 0)
94 {
95 BMCWEB_LOG_ERROR("Failed to load upgrade header");
96 return;
97 }
98 // Create the control stream
99 streams[0];
100
101 if (sendServerConnectionHeader() != 0)
102 {
103 BMCWEB_LOG_ERROR("send_server_connection_header failed");
104 return;
105 }
106 doRead();
107 }
108
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800109 int sendServerConnectionHeader()
110 {
Ed Tanous62598e32023-07-17 17:06:25 -0700111 BMCWEB_LOG_DEBUG("send_server_connection_header()");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800112
113 uint32_t maxStreams = 4;
114 std::array<nghttp2_settings_entry, 2> iv = {
115 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
116 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
117 int rv = ngSession.submitSettings(iv);
118 if (rv != 0)
119 {
Ed Tanous62598e32023-07-17 17:06:25 -0700120 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800121 return -1;
122 }
Ed Tanousd0882182024-01-26 23:45:25 -0800123 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800124 return 0;
125 }
126
Patrick Williams504af5a2025-02-03 14:29:03 -0500127 static ssize_t fileReadCallback(
128 nghttp2_session* /* session */, int32_t streamId, uint8_t* buf,
129 size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/,
130 void* userPtr)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800131 {
Ed Tanousf42e8592023-08-25 10:47:44 -0700132 self_type& self = userPtrToSelf(userPtr);
133
134 auto streamIt = self.streams.find(streamId);
135 if (streamIt == self.streams.end())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800136 {
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800137 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
138 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700139 Http2StreamData& stream = streamIt->second;
Ed Tanous62598e32023-07-17 17:06:25 -0700140 BMCWEB_LOG_DEBUG("File read callback length: {}", length);
Ed Tanousd547d8d2024-03-16 18:04:41 -0700141 if (!stream.writer)
142 {
143 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
144 }
Ed Tanous52e31622024-01-23 16:31:11 -0800145 boost::beast::error_code ec;
146 boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
147 stream.writer->getWithMaxSize(ec, length);
148 if (ec)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700149 {
Ed Tanous325310d2024-03-15 09:05:04 -0700150 BMCWEB_LOG_CRITICAL("Failed to get buffer");
Ed Tanous27b0cf92023-08-07 12:02:40 -0700151 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
152 }
Ed Tanous52e31622024-01-23 16:31:11 -0800153 if (!out)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800154 {
Ed Tanous325310d2024-03-15 09:05:04 -0700155 BMCWEB_LOG_ERROR("Empty file, setting EOF");
Ed Tanous52e31622024-01-23 16:31:11 -0800156 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
157 return 0;
158 }
159
160 BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
161 if (length < out->first.size())
162 {
Ed Tanous325310d2024-03-15 09:05:04 -0700163 BMCWEB_LOG_CRITICAL(
164 "Buffer overflow that should never happen happened");
Ed Tanous52e31622024-01-23 16:31:11 -0800165 // Should never happen because of length limit on get() above
166 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
167 }
Ed Tanous325310d2024-03-15 09:05:04 -0700168 boost::asio::mutable_buffer writeableBuf(buf, length);
Ed Tanous52e31622024-01-23 16:31:11 -0800169 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
Ed Tanous325310d2024-03-15 09:05:04 -0700170 size_t copied = boost::asio::buffer_copy(writeableBuf, out->first);
171 if (copied != out->first.size())
172 {
173 BMCWEB_LOG_ERROR(
174 "Couldn't copy all {} bytes into buffer, only copied {}",
175 out->first.size(), copied);
176 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
177 }
Ed Tanous52e31622024-01-23 16:31:11 -0800178
179 if (!out->second)
180 {
Ed Tanous325310d2024-03-15 09:05:04 -0700181 BMCWEB_LOG_DEBUG("Setting EOF flag");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800182 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800183 }
Ed Tanous325310d2024-03-15 09:05:04 -0700184 return static_cast<ssize_t>(copied);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800185 }
186
187 nghttp2_nv headerFromStringViews(std::string_view name,
Ed Tanous52e31622024-01-23 16:31:11 -0800188 std::string_view value, uint8_t flags)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800189 {
190 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
191 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
Ed Tanous52e31622024-01-23 16:31:11 -0800192 return {nameData, valueData, name.size(), value.size(), flags};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800193 }
194
195 int sendResponse(Response& completedRes, int32_t streamId)
196 {
Ed Tanous62598e32023-07-17 17:06:25 -0700197 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800198
199 auto it = streams.find(streamId);
200 if (it == streams.end())
201 {
202 close();
203 return -1;
204 }
Ed Tanous499b5b42024-04-06 08:39:18 -0700205 Http2StreamData& stream = it->second;
206 Response& res = stream.res;
207 res = std::move(completedRes);
Ed Tanous499b5b42024-04-06 08:39:18 -0700208
Ed Tanousb2539062024-03-12 16:58:35 -0700209 completeResponseFields(stream.accept, stream.acceptEnc, res);
Ed Tanous499b5b42024-04-06 08:39:18 -0700210 res.addHeader(boost::beast::http::field::date, getCachedDateStr());
211 res.preparePayload();
212
213 boost::beast::http::fields& fields = res.fields();
214 std::string code = std::to_string(res.resultInt());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800215 std::vector<nghttp2_nv> hdr;
Ed Tanous52e31622024-01-23 16:31:11 -0800216 hdr.emplace_back(
217 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800218 for (const boost::beast::http::fields::value_type& header : fields)
219 {
Ed Tanous52e31622024-01-23 16:31:11 -0800220 hdr.emplace_back(headerFromStringViews(
Ed Tanousd0882182024-01-26 23:45:25 -0800221 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800222 }
Ed Tanousb2896142024-01-31 15:25:47 -0800223 http::response<bmcweb::HttpBody>& fbody = res.response;
Ed Tanous52e31622024-01-23 16:31:11 -0800224 stream.writer.emplace(fbody.base(), fbody.body());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800225
226 nghttp2_data_provider dataPrd{
Ed Tanousf42e8592023-08-25 10:47:44 -0700227 .source = {.fd = 0},
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800228 .read_callback = fileReadCallback,
229 };
230
231 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
232 if (rv != 0)
233 {
Ed Tanous62598e32023-07-17 17:06:25 -0700234 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800235 close();
236 return -1;
237 }
Ed Tanousd0882182024-01-26 23:45:25 -0800238 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800239
240 return 0;
241 }
242
243 nghttp2_session initializeNghttp2Session()
244 {
245 nghttp2_session_callbacks callbacks;
246 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
247 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
248 callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
249 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
Ed Tanous325310d2024-03-15 09:05:04 -0700250 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800251
252 nghttp2_session session(callbacks);
253 session.setUserData(this);
254
255 return session;
256 }
257
258 int onRequestRecv(int32_t streamId)
259 {
Ed Tanous62598e32023-07-17 17:06:25 -0700260 BMCWEB_LOG_DEBUG("on_request_recv");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800261
262 auto it = streams.find(streamId);
263 if (it == streams.end())
264 {
265 close();
266 return -1;
267 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700268 auto& reqReader = it->second.reqReader;
269 if (reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700270 {
271 boost::beast::error_code ec;
Ed Tanousdaadfb22024-12-20 09:25:54 -0800272 bmcweb::HttpBody::reader::finish(ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700273 if (ec)
274 {
275 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
276 close();
277 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
278 }
279 }
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700280 crow::Request& thisReq = *it->second.req;
Ed Tanousb2539062024-03-12 16:58:35 -0700281 using boost::beast::http::field;
282 it->second.accept = thisReq.getHeaderValue(field::accept);
283 it->second.acceptEnc = thisReq.getHeaderValue(field::accept_encoding);
Ed Tanous89cda632024-04-16 08:45:54 -0700284
Ed Tanous62598e32023-07-17 17:06:25 -0700285 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
286 thisReq.url().encoded_path());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800287
Ed Tanousf42e8592023-08-25 10:47:44 -0700288 crow::Response& thisRes = it->second.res;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800289
290 thisRes.setCompleteRequestHandler(
291 [this, streamId](Response& completeRes) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400292 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
293 if (sendResponse(completeRes, streamId) != 0)
294 {
295 close();
296 return;
297 }
298 });
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800299 auto asyncResp =
Ed Tanousf42e8592023-08-25 10:47:44 -0700300 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
Ed Tanous83328312024-05-09 15:48:09 -0700301 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
Ed Tanous325310d2024-03-15 09:05:04 -0700302 {
Ed Tanous83328312024-05-09 15:48:09 -0700303 thisReq.session = crow::authentication::authenticate(
304 {}, asyncResp->res, thisReq.method(), thisReq.req, nullptr);
305 if (!crow::authentication::isOnAllowlist(thisReq.url().path(),
306 thisReq.method()) &&
307 thisReq.session == nullptr)
Ed Tanous499b5b42024-04-06 08:39:18 -0700308 {
Ed Tanous83328312024-05-09 15:48:09 -0700309 BMCWEB_LOG_WARNING("Authentication failed");
310 forward_unauthorized::sendUnauthorized(
311 thisReq.url().encoded_path(),
312 thisReq.getHeaderValue("X-Requested-With"),
313 thisReq.getHeaderValue("Accept"), asyncResp->res);
314 return 0;
Ed Tanous499b5b42024-04-06 08:39:18 -0700315 }
Ed Tanous325310d2024-03-15 09:05:04 -0700316 }
Ed Tanous83328312024-05-09 15:48:09 -0700317 std::string_view expected =
318 thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
319 BMCWEB_LOG_DEBUG("Setting expected hash {}", expected);
320 if (!expected.empty())
321 {
322 asyncResp->res.setExpectedHash(expected);
323 }
324 handler->handle(it->second.req, asyncResp);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800325 return 0;
326 }
327
Ed Tanous325310d2024-03-15 09:05:04 -0700328 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
329 const uint8_t* data, size_t len)
330 {
331 auto thisStream = streams.find(streamId);
332 if (thisStream == streams.end())
333 {
334 BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
335 close();
336 return -1;
337 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700338
339 std::optional<bmcweb::HttpBody::reader>& reqReader =
340 thisStream->second.reqReader;
341 if (!reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700342 {
Ed Tanous8e5cc7b2024-04-02 11:00:54 -0700343 reqReader.emplace(
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700344 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
345 thisStream->second.req->req.body()));
Ed Tanous325310d2024-03-15 09:05:04 -0700346 }
347 boost::beast::error_code ec;
Ed Tanousd547d8d2024-03-16 18:04:41 -0700348 reqReader->put(boost::asio::const_buffer(data, len), ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700349 if (ec)
350 {
351 BMCWEB_LOG_CRITICAL("Failed to write payload");
352 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
353 }
354 return 0;
355 }
356
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400357 static int onDataChunkRecvStatic(
358 nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
359 const uint8_t* data, size_t len, void* userData)
Ed Tanous325310d2024-03-15 09:05:04 -0700360 {
361 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
362 if (userData == nullptr)
363 {
364 BMCWEB_LOG_CRITICAL("user data was null?");
365 return NGHTTP2_ERR_CALLBACK_FAILURE;
366 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400367 return userPtrToSelf(userData).onDataChunkRecvCallback(
368 flags, streamId, data, len);
Ed Tanous325310d2024-03-15 09:05:04 -0700369 }
370
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800371 int onFrameRecvCallback(const nghttp2_frame& frame)
372 {
Ed Tanous62598e32023-07-17 17:06:25 -0700373 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800374 switch (frame.hd.type)
375 {
376 case NGHTTP2_DATA:
377 case NGHTTP2_HEADERS:
378 // Check that the client request has finished
379 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
380 {
381 return onRequestRecv(frame.hd.stream_id);
382 }
383 break;
384 default:
385 break;
386 }
387 return 0;
388 }
389
390 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
391 const nghttp2_frame* frame,
392 void* userData)
393 {
Ed Tanous62598e32023-07-17 17:06:25 -0700394 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800395 if (userData == nullptr)
396 {
Ed Tanous62598e32023-07-17 17:06:25 -0700397 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800398 return NGHTTP2_ERR_CALLBACK_FAILURE;
399 }
400 if (frame == nullptr)
401 {
Ed Tanous62598e32023-07-17 17:06:25 -0700402 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800403 return NGHTTP2_ERR_CALLBACK_FAILURE;
404 }
405 return userPtrToSelf(userData).onFrameRecvCallback(*frame);
406 }
407
408 static self_type& userPtrToSelf(void* userData)
409 {
410 // This method exists to keep the unsafe reinterpret cast in one
411 // place.
412 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
413 return *reinterpret_cast<self_type*>(userData);
414 }
415
416 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
417 int32_t streamId,
418 uint32_t /*unused*/, void* userData)
419 {
Ed Tanous62598e32023-07-17 17:06:25 -0700420 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800421 if (userData == nullptr)
422 {
Ed Tanous62598e32023-07-17 17:06:25 -0700423 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800424 return NGHTTP2_ERR_CALLBACK_FAILURE;
425 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700426 if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800427 {
428 return -1;
429 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800430 return 0;
431 }
432
433 int onHeaderCallback(const nghttp2_frame& frame,
434 std::span<const uint8_t> name,
435 std::span<const uint8_t> value)
436 {
437 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
438 std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
439 name.size());
440 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
441 std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
442 value.size());
443
Ed Tanous62598e32023-07-17 17:06:25 -0700444 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
445 valueSv);
Ed Tanousa07e9812024-03-19 10:31:13 -0700446 if (frame.hd.type != NGHTTP2_HEADERS)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800447 {
Ed Tanousa07e9812024-03-19 10:31:13 -0700448 return 0;
449 }
450 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
451 {
452 return 0;
453 }
454 auto thisStream = streams.find(frame.hd.stream_id);
455 if (thisStream == streams.end())
456 {
457 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
458 close();
459 return -1;
460 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800461
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700462 crow::Request& thisReq = *thisStream->second.req;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800463
Ed Tanousa07e9812024-03-19 10:31:13 -0700464 if (nameSv == ":path")
465 {
466 thisReq.target(valueSv);
467 }
468 else if (nameSv == ":method")
469 {
470 boost::beast::http::verb verb =
471 boost::beast::http::string_to_verb(valueSv);
472 if (verb == boost::beast::http::verb::unknown)
473 {
474 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
Ed Tanous50bfc912024-07-29 14:20:50 -0700475 verb = boost::beast::http::verb::trace;
Ed Tanousa07e9812024-03-19 10:31:13 -0700476 }
Myung Bae1873a042024-04-01 09:27:39 -0500477 thisReq.method(verb);
Ed Tanousa07e9812024-03-19 10:31:13 -0700478 }
479 else if (nameSv == ":scheme")
480 {
481 // Nothing to check on scheme
482 }
483 else
484 {
Myung Bae1873a042024-04-01 09:27:39 -0500485 thisReq.addHeader(nameSv, valueSv);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800486 }
487 return 0;
488 }
489
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400490 static int onHeaderCallbackStatic(
491 nghttp2_session* /* session */, const nghttp2_frame* frame,
492 const uint8_t* name, size_t namelen, const uint8_t* value,
493 size_t vallen, uint8_t /* flags */, void* userData)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800494 {
495 if (userData == nullptr)
496 {
Ed Tanous62598e32023-07-17 17:06:25 -0700497 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800498 return NGHTTP2_ERR_CALLBACK_FAILURE;
499 }
500 if (frame == nullptr)
501 {
Ed Tanous62598e32023-07-17 17:06:25 -0700502 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800503 return NGHTTP2_ERR_CALLBACK_FAILURE;
504 }
505 if (name == nullptr)
506 {
Ed Tanous62598e32023-07-17 17:06:25 -0700507 BMCWEB_LOG_CRITICAL("name was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800508 return NGHTTP2_ERR_CALLBACK_FAILURE;
509 }
510 if (value == nullptr)
511 {
Ed Tanous62598e32023-07-17 17:06:25 -0700512 BMCWEB_LOG_CRITICAL("value was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800513 return NGHTTP2_ERR_CALLBACK_FAILURE;
514 }
515 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
516 {value, vallen});
517 }
518
519 int onBeginHeadersCallback(const nghttp2_frame& frame)
520 {
521 if (frame.hd.type == NGHTTP2_HEADERS &&
522 frame.headers.cat == NGHTTP2_HCAT_REQUEST)
523 {
Ed Tanous62598e32023-07-17 17:06:25 -0700524 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800525
Ed Tanousb2539062024-03-12 16:58:35 -0700526 streams[frame.hd.stream_id];
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800527 }
528 return 0;
529 }
530
531 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
532 const nghttp2_frame* frame,
533 void* userData)
534 {
Ed Tanous62598e32023-07-17 17:06:25 -0700535 BMCWEB_LOG_DEBUG("on_begin_headers_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800536 if (userData == nullptr)
537 {
Ed Tanous62598e32023-07-17 17:06:25 -0700538 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800539 return NGHTTP2_ERR_CALLBACK_FAILURE;
540 }
541 if (frame == nullptr)
542 {
Ed Tanous62598e32023-07-17 17:06:25 -0700543 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800544 return NGHTTP2_ERR_CALLBACK_FAILURE;
545 }
546 return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
547 }
548
549 static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
550 const boost::system::error_code& ec,
551 size_t sendLength)
552 {
553 self->isWriting = false;
Ed Tanous62598e32023-07-17 17:06:25 -0700554 BMCWEB_LOG_DEBUG("Sent {}", sendLength);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800555 if (ec)
556 {
557 self->close();
558 return;
559 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800560 self->writeBuffer();
561 }
562
563 void writeBuffer()
564 {
565 if (isWriting)
566 {
567 return;
568 }
Ed Tanousd0882182024-01-26 23:45:25 -0800569 std::span<const uint8_t> data = ngSession.memSend();
570 if (data.empty())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800571 {
572 return;
573 }
574 isWriting = true;
Ed Tanousebe4c572025-02-08 14:29:53 -0800575 if (httpType == HttpType::HTTPS)
576 {
577 boost::asio::async_write(
578 adaptor, boost::asio::const_buffer(data.data(), data.size()),
579 std::bind_front(afterWriteBuffer, shared_from_this()));
580 }
581 else if (httpType == HttpType::HTTP)
582 {
583 boost::asio::async_write(
584 adaptor.next_layer(),
585 boost::asio::const_buffer(data.data(), data.size()),
586 std::bind_front(afterWriteBuffer, shared_from_this()));
587 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800588 }
589
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800590 void close()
591 {
Ed Tanousebe4c572025-02-08 14:29:53 -0800592 adaptor.next_layer().close();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800593 }
594
Ed Tanousd0882182024-01-26 23:45:25 -0800595 void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
596 const boost::system::error_code& ec,
597 size_t bytesTransferred)
598 {
599 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
600 bytesTransferred);
601
602 if (ec)
603 {
604 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
605 ec.message());
606 close();
607 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
608 return;
609 }
610 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
611
612 ssize_t readLen = ngSession.memRecv(bufferSpan);
613 if (readLen < 0)
614 {
615 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
616 close();
617 return;
618 }
619 writeBuffer();
620
621 doRead();
622 }
623
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800624 void doRead()
625 {
Ed Tanous62598e32023-07-17 17:06:25 -0700626 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
Ed Tanousebe4c572025-02-08 14:29:53 -0800627 if (httpType == HttpType::HTTPS)
628 {
629 adaptor.async_read_some(boost::asio::buffer(inBuffer),
630 std::bind_front(&self_type::afterDoRead,
631 this, shared_from_this()));
632 }
633 else if (httpType == HttpType::HTTP)
634 {
635 adaptor.next_layer().async_read_some(
636 boost::asio::buffer(inBuffer),
637 std::bind_front(&self_type::afterDoRead, this,
638 shared_from_this()));
639 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800640 }
641
642 // A mapping from http2 stream ID to Stream Data
Ed Tanous52e31622024-01-23 16:31:11 -0800643 std::map<int32_t, Http2StreamData> streams;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800644
Ed Tanousd0882182024-01-26 23:45:25 -0800645 std::array<uint8_t, 8192> inBuffer{};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800646
Ed Tanousebe4c572025-02-08 14:29:53 -0800647 HttpType httpType = HttpType::BOTH;
648 boost::asio::ssl::stream<Adaptor> adaptor;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800649 bool isWriting = false;
650
651 nghttp2_session ngSession;
652
653 Handler* handler;
654 std::function<std::string()>& getCachedDateStr;
655
656 using std::enable_shared_from_this<
657 HTTP2Connection<Adaptor, Handler>>::shared_from_this;
658
659 using std::enable_shared_from_this<
660 HTTP2Connection<Adaptor, Handler>>::weak_from_this;
661};
662} // namespace crow