blob: f0826d251822bb5c6c13559c96f3d9b5d7b54804 [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 Tanousd7857202025-01-28 15:32:26 -080011#include "http_request.hpp"
Ed Tanousfca2cbe2021-01-28 14:49:59 -080012#include "http_response.hpp"
Ed Tanousfca2cbe2021-01-28 14:49:59 -080013#include "logging.hpp"
Ed Tanousfca2cbe2021-01-28 14:49:59 -080014
Ed Tanousd7857202025-01-28 15:32:26 -080015// NOLINTNEXTLINE(misc-include-cleaner)
16#include "nghttp2_adapters.hpp"
17
18#include <nghttp2/nghttp2.h>
19#include <unistd.h>
20
21#include <boost/asio/buffer.hpp>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080022#include <boost/asio/ip/tcp.hpp>
23#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>
43#include <utility>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080044#include <vector>
45
46namespace crow
47{
48
49struct Http2StreamData
50{
Jonathan Doman102a4cd2024-04-15 16:56:23 -070051 std::shared_ptr<Request> req = std::make_shared<Request>();
Ed Tanous325310d2024-03-15 09:05:04 -070052 std::optional<bmcweb::HttpBody::reader> reqReader;
Ed Tanous89cda632024-04-16 08:45:54 -070053 std::string accept;
Ed Tanous47f29342024-03-19 12:18:06 -070054 Response res;
Ed Tanousb2896142024-01-31 15:25:47 -080055 std::optional<bmcweb::HttpBody::writer> writer;
Ed Tanousfca2cbe2021-01-28 14:49:59 -080056};
57
58template <typename Adaptor, typename Handler>
59class HTTP2Connection :
60 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
61{
62 using self_type = HTTP2Connection<Adaptor, Handler>;
63
64 public:
65 HTTP2Connection(Adaptor&& adaptorIn, Handler* handlerIn,
Ed Tanous52e31622024-01-23 16:31:11 -080066 std::function<std::string()>& getCachedDateStrF) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040067 adaptor(std::move(adaptorIn)), ngSession(initializeNghttp2Session()),
68 handler(handlerIn), getCachedDateStr(getCachedDateStrF)
Ed Tanousfca2cbe2021-01-28 14:49:59 -080069 {}
70
71 void start()
72 {
73 // Create the control stream
Ed Tanousf42e8592023-08-25 10:47:44 -070074 streams[0];
Ed Tanousfca2cbe2021-01-28 14:49:59 -080075
76 if (sendServerConnectionHeader() != 0)
77 {
Ed Tanous62598e32023-07-17 17:06:25 -070078 BMCWEB_LOG_ERROR("send_server_connection_header failed");
Ed Tanousfca2cbe2021-01-28 14:49:59 -080079 return;
80 }
81 doRead();
82 }
83
84 int sendServerConnectionHeader()
85 {
Ed Tanous62598e32023-07-17 17:06:25 -070086 BMCWEB_LOG_DEBUG("send_server_connection_header()");
Ed Tanousfca2cbe2021-01-28 14:49:59 -080087
88 uint32_t maxStreams = 4;
89 std::array<nghttp2_settings_entry, 2> iv = {
90 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
91 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
92 int rv = ngSession.submitSettings(iv);
93 if (rv != 0)
94 {
Ed Tanous62598e32023-07-17 17:06:25 -070095 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -080096 return -1;
97 }
Ed Tanousd0882182024-01-26 23:45:25 -080098 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -080099 return 0;
100 }
101
Patrick Williams504af5a2025-02-03 14:29:03 -0500102 static ssize_t fileReadCallback(
103 nghttp2_session* /* session */, int32_t streamId, uint8_t* buf,
104 size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/,
105 void* userPtr)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800106 {
Ed Tanousf42e8592023-08-25 10:47:44 -0700107 self_type& self = userPtrToSelf(userPtr);
108
109 auto streamIt = self.streams.find(streamId);
110 if (streamIt == self.streams.end())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800111 {
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800112 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
113 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700114 Http2StreamData& stream = streamIt->second;
Ed Tanous62598e32023-07-17 17:06:25 -0700115 BMCWEB_LOG_DEBUG("File read callback length: {}", length);
Ed Tanousd547d8d2024-03-16 18:04:41 -0700116 if (!stream.writer)
117 {
118 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
119 }
Ed Tanous52e31622024-01-23 16:31:11 -0800120 boost::beast::error_code ec;
121 boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
122 stream.writer->getWithMaxSize(ec, length);
123 if (ec)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700124 {
Ed Tanous325310d2024-03-15 09:05:04 -0700125 BMCWEB_LOG_CRITICAL("Failed to get buffer");
Ed Tanous27b0cf92023-08-07 12:02:40 -0700126 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
127 }
Ed Tanous52e31622024-01-23 16:31:11 -0800128 if (!out)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800129 {
Ed Tanous325310d2024-03-15 09:05:04 -0700130 BMCWEB_LOG_ERROR("Empty file, setting EOF");
Ed Tanous52e31622024-01-23 16:31:11 -0800131 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
132 return 0;
133 }
134
135 BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
136 if (length < out->first.size())
137 {
Ed Tanous325310d2024-03-15 09:05:04 -0700138 BMCWEB_LOG_CRITICAL(
139 "Buffer overflow that should never happen happened");
Ed Tanous52e31622024-01-23 16:31:11 -0800140 // Should never happen because of length limit on get() above
141 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
142 }
Ed Tanous325310d2024-03-15 09:05:04 -0700143 boost::asio::mutable_buffer writeableBuf(buf, length);
Ed Tanous52e31622024-01-23 16:31:11 -0800144 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
Ed Tanous325310d2024-03-15 09:05:04 -0700145 size_t copied = boost::asio::buffer_copy(writeableBuf, out->first);
146 if (copied != out->first.size())
147 {
148 BMCWEB_LOG_ERROR(
149 "Couldn't copy all {} bytes into buffer, only copied {}",
150 out->first.size(), copied);
151 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
152 }
Ed Tanous52e31622024-01-23 16:31:11 -0800153
154 if (!out->second)
155 {
Ed Tanous325310d2024-03-15 09:05:04 -0700156 BMCWEB_LOG_DEBUG("Setting EOF flag");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800157 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800158 }
Ed Tanous325310d2024-03-15 09:05:04 -0700159 return static_cast<ssize_t>(copied);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800160 }
161
162 nghttp2_nv headerFromStringViews(std::string_view name,
Ed Tanous52e31622024-01-23 16:31:11 -0800163 std::string_view value, uint8_t flags)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800164 {
165 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
166 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
Ed Tanous52e31622024-01-23 16:31:11 -0800167 return {nameData, valueData, name.size(), value.size(), flags};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800168 }
169
170 int sendResponse(Response& completedRes, int32_t streamId)
171 {
Ed Tanous62598e32023-07-17 17:06:25 -0700172 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800173
174 auto it = streams.find(streamId);
175 if (it == streams.end())
176 {
177 close();
178 return -1;
179 }
Ed Tanous499b5b42024-04-06 08:39:18 -0700180 Http2StreamData& stream = it->second;
181 Response& res = stream.res;
182 res = std::move(completedRes);
Ed Tanous499b5b42024-04-06 08:39:18 -0700183
Ed Tanous89cda632024-04-16 08:45:54 -0700184 completeResponseFields(stream.accept, res);
Ed Tanous499b5b42024-04-06 08:39:18 -0700185 res.addHeader(boost::beast::http::field::date, getCachedDateStr());
186 res.preparePayload();
187
188 boost::beast::http::fields& fields = res.fields();
189 std::string code = std::to_string(res.resultInt());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800190 std::vector<nghttp2_nv> hdr;
Ed Tanous52e31622024-01-23 16:31:11 -0800191 hdr.emplace_back(
192 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800193 for (const boost::beast::http::fields::value_type& header : fields)
194 {
Ed Tanous52e31622024-01-23 16:31:11 -0800195 hdr.emplace_back(headerFromStringViews(
Ed Tanousd0882182024-01-26 23:45:25 -0800196 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800197 }
Ed Tanousb2896142024-01-31 15:25:47 -0800198 http::response<bmcweb::HttpBody>& fbody = res.response;
Ed Tanous52e31622024-01-23 16:31:11 -0800199 stream.writer.emplace(fbody.base(), fbody.body());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800200
201 nghttp2_data_provider dataPrd{
Ed Tanousf42e8592023-08-25 10:47:44 -0700202 .source = {.fd = 0},
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800203 .read_callback = fileReadCallback,
204 };
205
206 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
207 if (rv != 0)
208 {
Ed Tanous62598e32023-07-17 17:06:25 -0700209 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800210 close();
211 return -1;
212 }
Ed Tanousd0882182024-01-26 23:45:25 -0800213 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800214
215 return 0;
216 }
217
218 nghttp2_session initializeNghttp2Session()
219 {
220 nghttp2_session_callbacks callbacks;
221 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
222 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
223 callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
224 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
Ed Tanous325310d2024-03-15 09:05:04 -0700225 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800226
227 nghttp2_session session(callbacks);
228 session.setUserData(this);
229
230 return session;
231 }
232
233 int onRequestRecv(int32_t streamId)
234 {
Ed Tanous62598e32023-07-17 17:06:25 -0700235 BMCWEB_LOG_DEBUG("on_request_recv");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800236
237 auto it = streams.find(streamId);
238 if (it == streams.end())
239 {
240 close();
241 return -1;
242 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700243 auto& reqReader = it->second.reqReader;
244 if (reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700245 {
246 boost::beast::error_code ec;
Ed Tanousdaadfb22024-12-20 09:25:54 -0800247 bmcweb::HttpBody::reader::finish(ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700248 if (ec)
249 {
250 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
251 close();
252 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
253 }
254 }
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700255 crow::Request& thisReq = *it->second.req;
Ed Tanous8e8245d2024-04-11 22:21:38 -0700256 thisReq.ioService = static_cast<decltype(thisReq.ioService)>(
257 &adaptor.get_executor().context());
Ed Tanous89cda632024-04-16 08:45:54 -0700258
259 it->second.accept = thisReq.getHeaderValue("Accept");
260
Ed Tanous62598e32023-07-17 17:06:25 -0700261 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
262 thisReq.url().encoded_path());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800263
Ed Tanousf42e8592023-08-25 10:47:44 -0700264 crow::Response& thisRes = it->second.res;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800265
266 thisRes.setCompleteRequestHandler(
267 [this, streamId](Response& completeRes) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400268 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
269 if (sendResponse(completeRes, streamId) != 0)
270 {
271 close();
272 return;
273 }
274 });
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800275 auto asyncResp =
Ed Tanousf42e8592023-08-25 10:47:44 -0700276 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
Ed Tanous83328312024-05-09 15:48:09 -0700277 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
Ed Tanous325310d2024-03-15 09:05:04 -0700278 {
Ed Tanous83328312024-05-09 15:48:09 -0700279 thisReq.session = crow::authentication::authenticate(
280 {}, asyncResp->res, thisReq.method(), thisReq.req, nullptr);
281 if (!crow::authentication::isOnAllowlist(thisReq.url().path(),
282 thisReq.method()) &&
283 thisReq.session == nullptr)
Ed Tanous499b5b42024-04-06 08:39:18 -0700284 {
Ed Tanous83328312024-05-09 15:48:09 -0700285 BMCWEB_LOG_WARNING("Authentication failed");
286 forward_unauthorized::sendUnauthorized(
287 thisReq.url().encoded_path(),
288 thisReq.getHeaderValue("X-Requested-With"),
289 thisReq.getHeaderValue("Accept"), asyncResp->res);
290 return 0;
Ed Tanous499b5b42024-04-06 08:39:18 -0700291 }
Ed Tanous325310d2024-03-15 09:05:04 -0700292 }
Ed Tanous83328312024-05-09 15:48:09 -0700293 std::string_view expected =
294 thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
295 BMCWEB_LOG_DEBUG("Setting expected hash {}", expected);
296 if (!expected.empty())
297 {
298 asyncResp->res.setExpectedHash(expected);
299 }
300 handler->handle(it->second.req, asyncResp);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800301 return 0;
302 }
303
Ed Tanous325310d2024-03-15 09:05:04 -0700304 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
305 const uint8_t* data, size_t len)
306 {
307 auto thisStream = streams.find(streamId);
308 if (thisStream == streams.end())
309 {
310 BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
311 close();
312 return -1;
313 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700314
315 std::optional<bmcweb::HttpBody::reader>& reqReader =
316 thisStream->second.reqReader;
317 if (!reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700318 {
Ed Tanous8e5cc7b2024-04-02 11:00:54 -0700319 reqReader.emplace(
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700320 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
321 thisStream->second.req->req.body()));
Ed Tanous325310d2024-03-15 09:05:04 -0700322 }
323 boost::beast::error_code ec;
Ed Tanousd547d8d2024-03-16 18:04:41 -0700324 reqReader->put(boost::asio::const_buffer(data, len), ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700325 if (ec)
326 {
327 BMCWEB_LOG_CRITICAL("Failed to write payload");
328 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
329 }
330 return 0;
331 }
332
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400333 static int onDataChunkRecvStatic(
334 nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
335 const uint8_t* data, size_t len, void* userData)
Ed Tanous325310d2024-03-15 09:05:04 -0700336 {
337 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
338 if (userData == nullptr)
339 {
340 BMCWEB_LOG_CRITICAL("user data was null?");
341 return NGHTTP2_ERR_CALLBACK_FAILURE;
342 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400343 return userPtrToSelf(userData).onDataChunkRecvCallback(
344 flags, streamId, data, len);
Ed Tanous325310d2024-03-15 09:05:04 -0700345 }
346
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800347 int onFrameRecvCallback(const nghttp2_frame& frame)
348 {
Ed Tanous62598e32023-07-17 17:06:25 -0700349 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800350 switch (frame.hd.type)
351 {
352 case NGHTTP2_DATA:
353 case NGHTTP2_HEADERS:
354 // Check that the client request has finished
355 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
356 {
357 return onRequestRecv(frame.hd.stream_id);
358 }
359 break;
360 default:
361 break;
362 }
363 return 0;
364 }
365
366 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
367 const nghttp2_frame* frame,
368 void* userData)
369 {
Ed Tanous62598e32023-07-17 17:06:25 -0700370 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800371 if (userData == nullptr)
372 {
Ed Tanous62598e32023-07-17 17:06:25 -0700373 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800374 return NGHTTP2_ERR_CALLBACK_FAILURE;
375 }
376 if (frame == nullptr)
377 {
Ed Tanous62598e32023-07-17 17:06:25 -0700378 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800379 return NGHTTP2_ERR_CALLBACK_FAILURE;
380 }
381 return userPtrToSelf(userData).onFrameRecvCallback(*frame);
382 }
383
384 static self_type& userPtrToSelf(void* userData)
385 {
386 // This method exists to keep the unsafe reinterpret cast in one
387 // place.
388 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
389 return *reinterpret_cast<self_type*>(userData);
390 }
391
392 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
393 int32_t streamId,
394 uint32_t /*unused*/, void* userData)
395 {
Ed Tanous62598e32023-07-17 17:06:25 -0700396 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800397 if (userData == nullptr)
398 {
Ed Tanous62598e32023-07-17 17:06:25 -0700399 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800400 return NGHTTP2_ERR_CALLBACK_FAILURE;
401 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700402 if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800403 {
404 return -1;
405 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800406 return 0;
407 }
408
409 int onHeaderCallback(const nghttp2_frame& frame,
410 std::span<const uint8_t> name,
411 std::span<const uint8_t> value)
412 {
413 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
414 std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
415 name.size());
416 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
417 std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
418 value.size());
419
Ed Tanous62598e32023-07-17 17:06:25 -0700420 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
421 valueSv);
Ed Tanousa07e9812024-03-19 10:31:13 -0700422 if (frame.hd.type != NGHTTP2_HEADERS)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800423 {
Ed Tanousa07e9812024-03-19 10:31:13 -0700424 return 0;
425 }
426 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
427 {
428 return 0;
429 }
430 auto thisStream = streams.find(frame.hd.stream_id);
431 if (thisStream == streams.end())
432 {
433 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
434 close();
435 return -1;
436 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800437
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700438 crow::Request& thisReq = *thisStream->second.req;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800439
Ed Tanousa07e9812024-03-19 10:31:13 -0700440 if (nameSv == ":path")
441 {
442 thisReq.target(valueSv);
443 }
444 else if (nameSv == ":method")
445 {
446 boost::beast::http::verb verb =
447 boost::beast::http::string_to_verb(valueSv);
448 if (verb == boost::beast::http::verb::unknown)
449 {
450 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
Ed Tanous50bfc912024-07-29 14:20:50 -0700451 verb = boost::beast::http::verb::trace;
Ed Tanousa07e9812024-03-19 10:31:13 -0700452 }
Myung Bae1873a042024-04-01 09:27:39 -0500453 thisReq.method(verb);
Ed Tanousa07e9812024-03-19 10:31:13 -0700454 }
455 else if (nameSv == ":scheme")
456 {
457 // Nothing to check on scheme
458 }
459 else
460 {
Myung Bae1873a042024-04-01 09:27:39 -0500461 thisReq.addHeader(nameSv, valueSv);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800462 }
463 return 0;
464 }
465
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400466 static int onHeaderCallbackStatic(
467 nghttp2_session* /* session */, const nghttp2_frame* frame,
468 const uint8_t* name, size_t namelen, const uint8_t* value,
469 size_t vallen, uint8_t /* flags */, void* userData)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800470 {
471 if (userData == nullptr)
472 {
Ed Tanous62598e32023-07-17 17:06:25 -0700473 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800474 return NGHTTP2_ERR_CALLBACK_FAILURE;
475 }
476 if (frame == nullptr)
477 {
Ed Tanous62598e32023-07-17 17:06:25 -0700478 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800479 return NGHTTP2_ERR_CALLBACK_FAILURE;
480 }
481 if (name == nullptr)
482 {
Ed Tanous62598e32023-07-17 17:06:25 -0700483 BMCWEB_LOG_CRITICAL("name was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800484 return NGHTTP2_ERR_CALLBACK_FAILURE;
485 }
486 if (value == nullptr)
487 {
Ed Tanous62598e32023-07-17 17:06:25 -0700488 BMCWEB_LOG_CRITICAL("value was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800489 return NGHTTP2_ERR_CALLBACK_FAILURE;
490 }
491 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
492 {value, vallen});
493 }
494
495 int onBeginHeadersCallback(const nghttp2_frame& frame)
496 {
497 if (frame.hd.type == NGHTTP2_HEADERS &&
498 frame.headers.cat == NGHTTP2_HCAT_REQUEST)
499 {
Ed Tanous62598e32023-07-17 17:06:25 -0700500 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800501
Ed Tanous3a499412024-06-05 16:05:35 -0700502 streams.emplace(frame.hd.stream_id, Http2StreamData());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800503 }
504 return 0;
505 }
506
507 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
508 const nghttp2_frame* frame,
509 void* userData)
510 {
Ed Tanous62598e32023-07-17 17:06:25 -0700511 BMCWEB_LOG_DEBUG("on_begin_headers_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800512 if (userData == nullptr)
513 {
Ed Tanous62598e32023-07-17 17:06:25 -0700514 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800515 return NGHTTP2_ERR_CALLBACK_FAILURE;
516 }
517 if (frame == nullptr)
518 {
Ed Tanous62598e32023-07-17 17:06:25 -0700519 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800520 return NGHTTP2_ERR_CALLBACK_FAILURE;
521 }
522 return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
523 }
524
525 static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
526 const boost::system::error_code& ec,
527 size_t sendLength)
528 {
529 self->isWriting = false;
Ed Tanous62598e32023-07-17 17:06:25 -0700530 BMCWEB_LOG_DEBUG("Sent {}", sendLength);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800531 if (ec)
532 {
533 self->close();
534 return;
535 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800536 self->writeBuffer();
537 }
538
539 void writeBuffer()
540 {
541 if (isWriting)
542 {
543 return;
544 }
Ed Tanousd0882182024-01-26 23:45:25 -0800545 std::span<const uint8_t> data = ngSession.memSend();
546 if (data.empty())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800547 {
548 return;
549 }
550 isWriting = true;
Ed Tanous325310d2024-03-15 09:05:04 -0700551 boost::asio::async_write(
552 adaptor, boost::asio::const_buffer(data.data(), data.size()),
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800553 std::bind_front(afterWriteBuffer, shared_from_this()));
554 }
555
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800556 void close()
557 {
558 if constexpr (std::is_same_v<Adaptor,
Ed Tanous003301a2024-04-16 09:59:19 -0700559 boost::asio::ssl::stream<
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800560 boost::asio::ip::tcp::socket>>)
561 {
562 adaptor.next_layer().close();
563 }
564 else
565 {
566 adaptor.close();
567 }
568 }
569
Ed Tanousd0882182024-01-26 23:45:25 -0800570 void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
571 const boost::system::error_code& ec,
572 size_t bytesTransferred)
573 {
574 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
575 bytesTransferred);
576
577 if (ec)
578 {
579 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
580 ec.message());
581 close();
582 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
583 return;
584 }
585 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
586
587 ssize_t readLen = ngSession.memRecv(bufferSpan);
588 if (readLen < 0)
589 {
590 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
591 close();
592 return;
593 }
594 writeBuffer();
595
596 doRead();
597 }
598
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800599 void doRead()
600 {
Ed Tanous62598e32023-07-17 17:06:25 -0700601 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800602 adaptor.async_read_some(
Ed Tanousd0882182024-01-26 23:45:25 -0800603 boost::asio::buffer(inBuffer),
604 std::bind_front(&self_type::afterDoRead, this, shared_from_this()));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800605 }
606
607 // A mapping from http2 stream ID to Stream Data
Ed Tanous52e31622024-01-23 16:31:11 -0800608 std::map<int32_t, Http2StreamData> streams;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800609
Ed Tanousd0882182024-01-26 23:45:25 -0800610 std::array<uint8_t, 8192> inBuffer{};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800611
612 Adaptor adaptor;
613 bool isWriting = false;
614
615 nghttp2_session ngSession;
616
617 Handler* handler;
618 std::function<std::string()>& getCachedDateStr;
619
620 using std::enable_shared_from_this<
621 HTTP2Connection<Adaptor, Handler>>::shared_from_this;
622
623 using std::enable_shared_from_this<
624 HTTP2Connection<Adaptor, Handler>>::weak_from_this;
625};
626} // namespace crow