blob: a63b234575d4b7f04591a4b3b99489c606e92d41 [file] [log] [blame]
Ed Tanousfca2cbe2021-01-28 14:49:59 -08001#pragma once
2#include "bmcweb_config.h"
3
4#include "async_resp.hpp"
5#include "authentication.hpp"
6#include "complete_response_fields.hpp"
7#include "http_response.hpp"
8#include "http_utility.hpp"
9#include "logging.hpp"
10#include "mutual_tls.hpp"
11#include "nghttp2_adapters.hpp"
12#include "ssl_key_handler.hpp"
13#include "utility.hpp"
14
15#include <boost/algorithm/string/predicate.hpp>
16#include <boost/asio/io_context.hpp>
17#include <boost/asio/ip/tcp.hpp>
18#include <boost/asio/ssl/stream.hpp>
19#include <boost/asio/steady_timer.hpp>
20#include <boost/beast/core/multi_buffer.hpp>
21#include <boost/beast/http/error.hpp>
22#include <boost/beast/http/parser.hpp>
23#include <boost/beast/http/read.hpp>
24#include <boost/beast/http/serializer.hpp>
25#include <boost/beast/http/string_body.hpp>
26#include <boost/beast/http/write.hpp>
27#include <boost/beast/ssl/ssl_stream.hpp>
28#include <boost/beast/websocket.hpp>
29
30#include <atomic>
31#include <chrono>
32#include <vector>
33
34namespace crow
35{
36
37struct Http2StreamData
38{
39 crow::Request req{};
40 crow::Response res{};
41 size_t sentSofar = 0;
42};
43
44template <typename Adaptor, typename Handler>
45class HTTP2Connection :
46 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
47{
48 using self_type = HTTP2Connection<Adaptor, Handler>;
49
50 public:
51 HTTP2Connection(Adaptor&& adaptorIn, Handler* handlerIn,
52 std::function<std::string()>& getCachedDateStrF
53
54 ) :
55 adaptor(std::move(adaptorIn)),
56
57 ngSession(initializeNghttp2Session()),
58
59 handler(handlerIn), getCachedDateStr(getCachedDateStrF)
60 {}
61
62 void start()
63 {
64 // Create the control stream
65 streams.emplace(0, std::make_unique<Http2StreamData>());
66
67 if (sendServerConnectionHeader() != 0)
68 {
Ed Tanous62598e32023-07-17 17:06:25 -070069 BMCWEB_LOG_ERROR("send_server_connection_header failed");
Ed Tanousfca2cbe2021-01-28 14:49:59 -080070 return;
71 }
72 doRead();
73 }
74
75 int sendServerConnectionHeader()
76 {
Ed Tanous62598e32023-07-17 17:06:25 -070077 BMCWEB_LOG_DEBUG("send_server_connection_header()");
Ed Tanousfca2cbe2021-01-28 14:49:59 -080078
79 uint32_t maxStreams = 4;
80 std::array<nghttp2_settings_entry, 2> iv = {
81 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
82 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
83 int rv = ngSession.submitSettings(iv);
84 if (rv != 0)
85 {
Ed Tanous62598e32023-07-17 17:06:25 -070086 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -080087 return -1;
88 }
89 return 0;
90 }
91
92 static ssize_t fileReadCallback(nghttp2_session* /* session */,
93 int32_t /* stream_id */, uint8_t* buf,
94 size_t length, uint32_t* dataFlags,
95 nghttp2_data_source* source,
96 void* /*unused*/)
97 {
98 if (source == nullptr || source->ptr == nullptr)
99 {
Ed Tanous62598e32023-07-17 17:06:25 -0700100 BMCWEB_LOG_DEBUG("Source was null???");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800101 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
102 }
103
Ed Tanous62598e32023-07-17 17:06:25 -0700104 BMCWEB_LOG_DEBUG("File read callback length: {}", length);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800105 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
106 Http2StreamData* str = reinterpret_cast<Http2StreamData*>(source->ptr);
107 crow::Response& res = str->res;
108
Ed Tanous62598e32023-07-17 17:06:25 -0700109 BMCWEB_LOG_DEBUG("total: {} send_sofar: {}", res.body().size(),
110 str->sentSofar);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800111
112 size_t toSend = std::min(res.body().size() - str->sentSofar, length);
Ed Tanous62598e32023-07-17 17:06:25 -0700113 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", toSend);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800114
115 std::string::iterator bodyBegin = res.body().begin();
116 std::advance(bodyBegin, str->sentSofar);
117
118 memcpy(buf, &*bodyBegin, toSend);
119 str->sentSofar += toSend;
120
121 if (str->sentSofar >= res.body().size())
122 {
Ed Tanous62598e32023-07-17 17:06:25 -0700123 BMCWEB_LOG_DEBUG("Setting OEF flag");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800124 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
125 //*dataFlags |= NGHTTP2_DATA_FLAG_NO_COPY;
126 }
127 return static_cast<ssize_t>(toSend);
128 }
129
130 nghttp2_nv headerFromStringViews(std::string_view name,
131 std::string_view value)
132 {
133 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
134 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
135 return {nameData, valueData, name.size(), value.size(),
136 NGHTTP2_NV_FLAG_NONE};
137 }
138
139 int sendResponse(Response& completedRes, int32_t streamId)
140 {
Ed Tanous62598e32023-07-17 17:06:25 -0700141 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800142
143 auto it = streams.find(streamId);
144 if (it == streams.end())
145 {
146 close();
147 return -1;
148 }
149 Response& thisRes = it->second->res;
150 thisRes = std::move(completedRes);
151 crow::Request& thisReq = it->second->req;
152 std::vector<nghttp2_nv> hdr;
153
154 completeResponseFields(thisReq, thisRes);
155 thisRes.addHeader(boost::beast::http::field::date, getCachedDateStr());
156
157 boost::beast::http::fields& fields = thisRes.stringResponse->base();
158 std::string code = std::to_string(thisRes.stringResponse->result_int());
159 hdr.emplace_back(headerFromStringViews(":status", code));
160 for (const boost::beast::http::fields::value_type& header : fields)
161 {
162 hdr.emplace_back(
163 headerFromStringViews(header.name_string(), header.value()));
164 }
165 Http2StreamData* streamPtr = it->second.get();
166 streamPtr->sentSofar = 0;
167
168 nghttp2_data_provider dataPrd{
169 .source{
170 .ptr = streamPtr,
171 },
172 .read_callback = fileReadCallback,
173 };
174
175 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
176 if (rv != 0)
177 {
Ed Tanous62598e32023-07-17 17:06:25 -0700178 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800179 close();
180 return -1;
181 }
182 ngSession.send();
183
184 return 0;
185 }
186
187 nghttp2_session initializeNghttp2Session()
188 {
189 nghttp2_session_callbacks callbacks;
190 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
191 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
192 callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
193 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
194 callbacks.setSendCallback(onSendCallbackStatic);
195
196 nghttp2_session session(callbacks);
197 session.setUserData(this);
198
199 return session;
200 }
201
202 int onRequestRecv(int32_t streamId)
203 {
Ed Tanous62598e32023-07-17 17:06:25 -0700204 BMCWEB_LOG_DEBUG("on_request_recv");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800205
206 auto it = streams.find(streamId);
207 if (it == streams.end())
208 {
209 close();
210 return -1;
211 }
212
213 crow::Request& thisReq = it->second->req;
Ed Tanous62598e32023-07-17 17:06:25 -0700214 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
215 thisReq.url().encoded_path());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800216
217 crow::Response& thisRes = it->second->res;
218
219 thisRes.setCompleteRequestHandler(
220 [this, streamId](Response& completeRes) {
Ed Tanous62598e32023-07-17 17:06:25 -0700221 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800222 if (sendResponse(completeRes, streamId) != 0)
223 {
224 close();
225 return;
226 }
227 });
228 auto asyncResp =
229 std::make_shared<bmcweb::AsyncResp>(std::move(it->second->res));
230 handler->handle(thisReq, asyncResp);
231
232 return 0;
233 }
234
235 int onFrameRecvCallback(const nghttp2_frame& frame)
236 {
Ed Tanous62598e32023-07-17 17:06:25 -0700237 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800238 switch (frame.hd.type)
239 {
240 case NGHTTP2_DATA:
241 case NGHTTP2_HEADERS:
242 // Check that the client request has finished
243 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
244 {
245 return onRequestRecv(frame.hd.stream_id);
246 }
247 break;
248 default:
249 break;
250 }
251 return 0;
252 }
253
254 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
255 const nghttp2_frame* frame,
256 void* userData)
257 {
Ed Tanous62598e32023-07-17 17:06:25 -0700258 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800259 if (userData == nullptr)
260 {
Ed Tanous62598e32023-07-17 17:06:25 -0700261 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800262 return NGHTTP2_ERR_CALLBACK_FAILURE;
263 }
264 if (frame == nullptr)
265 {
Ed Tanous62598e32023-07-17 17:06:25 -0700266 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800267 return NGHTTP2_ERR_CALLBACK_FAILURE;
268 }
269 return userPtrToSelf(userData).onFrameRecvCallback(*frame);
270 }
271
272 static self_type& userPtrToSelf(void* userData)
273 {
274 // This method exists to keep the unsafe reinterpret cast in one
275 // place.
276 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
277 return *reinterpret_cast<self_type*>(userData);
278 }
279
280 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
281 int32_t streamId,
282 uint32_t /*unused*/, void* userData)
283 {
Ed Tanous62598e32023-07-17 17:06:25 -0700284 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800285 if (userData == nullptr)
286 {
Ed Tanous62598e32023-07-17 17:06:25 -0700287 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800288 return NGHTTP2_ERR_CALLBACK_FAILURE;
289 }
290 auto stream = userPtrToSelf(userData).streams.find(streamId);
291 if (stream == userPtrToSelf(userData).streams.end())
292 {
293 return -1;
294 }
295
296 userPtrToSelf(userData).streams.erase(streamId);
297 return 0;
298 }
299
300 int onHeaderCallback(const nghttp2_frame& frame,
301 std::span<const uint8_t> name,
302 std::span<const uint8_t> value)
303 {
304 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
305 std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
306 name.size());
307 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
308 std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
309 value.size());
310
Ed Tanous62598e32023-07-17 17:06:25 -0700311 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
312 valueSv);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800313
314 switch (frame.hd.type)
315 {
316 case NGHTTP2_HEADERS:
317 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
318 {
319 break;
320 }
321 auto thisStream = streams.find(frame.hd.stream_id);
322 if (thisStream == streams.end())
323 {
Ed Tanous62598e32023-07-17 17:06:25 -0700324 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800325 close();
326 return -1;
327 }
328
329 crow::Request& thisReq = thisStream->second->req;
330
331 if (nameSv == ":path")
332 {
333 thisReq.target(valueSv);
334 }
335 else if (nameSv == ":method")
336 {
337 boost::beast::http::verb verb =
338 boost::beast::http::string_to_verb(valueSv);
339 if (verb == boost::beast::http::verb::unknown)
340 {
Ed Tanous62598e32023-07-17 17:06:25 -0700341 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800342 close();
343 return -1;
344 }
345 thisReq.req.method(verb);
346 }
347 else if (nameSv == ":scheme")
348 {
349 // Nothing to check on scheme
350 }
351 else
352 {
353 thisReq.req.set(nameSv, valueSv);
354 }
355 break;
356 }
357 return 0;
358 }
359
360 static int onHeaderCallbackStatic(nghttp2_session* /* session */,
361 const nghttp2_frame* frame,
362 const uint8_t* name, size_t namelen,
363 const uint8_t* value, size_t vallen,
364 uint8_t /* flags */, void* userData)
365 {
366 if (userData == nullptr)
367 {
Ed Tanous62598e32023-07-17 17:06:25 -0700368 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800369 return NGHTTP2_ERR_CALLBACK_FAILURE;
370 }
371 if (frame == nullptr)
372 {
Ed Tanous62598e32023-07-17 17:06:25 -0700373 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800374 return NGHTTP2_ERR_CALLBACK_FAILURE;
375 }
376 if (name == nullptr)
377 {
Ed Tanous62598e32023-07-17 17:06:25 -0700378 BMCWEB_LOG_CRITICAL("name was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800379 return NGHTTP2_ERR_CALLBACK_FAILURE;
380 }
381 if (value == nullptr)
382 {
Ed Tanous62598e32023-07-17 17:06:25 -0700383 BMCWEB_LOG_CRITICAL("value was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800384 return NGHTTP2_ERR_CALLBACK_FAILURE;
385 }
386 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
387 {value, vallen});
388 }
389
390 int onBeginHeadersCallback(const nghttp2_frame& frame)
391 {
392 if (frame.hd.type == NGHTTP2_HEADERS &&
393 frame.headers.cat == NGHTTP2_HCAT_REQUEST)
394 {
Ed Tanous62598e32023-07-17 17:06:25 -0700395 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800396
397 std::pair<boost::container::flat_map<
398 int32_t, std::unique_ptr<Http2StreamData>>::iterator,
399 bool>
400 stream = streams.emplace(frame.hd.stream_id,
401 std::make_unique<Http2StreamData>());
402 // http2 is by definition always tls
403 stream.first->second->req.isSecure = true;
404 }
405 return 0;
406 }
407
408 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
409 const nghttp2_frame* frame,
410 void* userData)
411 {
Ed Tanous62598e32023-07-17 17:06:25 -0700412 BMCWEB_LOG_DEBUG("on_begin_headers_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800413 if (userData == nullptr)
414 {
Ed Tanous62598e32023-07-17 17:06:25 -0700415 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800416 return NGHTTP2_ERR_CALLBACK_FAILURE;
417 }
418 if (frame == nullptr)
419 {
Ed Tanous62598e32023-07-17 17:06:25 -0700420 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800421 return NGHTTP2_ERR_CALLBACK_FAILURE;
422 }
423 return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
424 }
425
426 static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
427 const boost::system::error_code& ec,
428 size_t sendLength)
429 {
430 self->isWriting = false;
Ed Tanous62598e32023-07-17 17:06:25 -0700431 BMCWEB_LOG_DEBUG("Sent {}", sendLength);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800432 if (ec)
433 {
434 self->close();
435 return;
436 }
437 self->sendBuffer.consume(sendLength);
438 self->writeBuffer();
439 }
440
441 void writeBuffer()
442 {
443 if (isWriting)
444 {
445 return;
446 }
447 if (sendBuffer.size() <= 0)
448 {
449 return;
450 }
451 isWriting = true;
452 adaptor.async_write_some(
453 sendBuffer.data(),
454 std::bind_front(afterWriteBuffer, shared_from_this()));
455 }
456
457 ssize_t onSendCallback(nghttp2_session* /*session */, const uint8_t* data,
458 size_t length, int /* flags */)
459 {
Ed Tanous62598e32023-07-17 17:06:25 -0700460 BMCWEB_LOG_DEBUG("On send callback size={}", length);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800461 size_t copied = boost::asio::buffer_copy(
462 sendBuffer.prepare(length), boost::asio::buffer(data, length));
463 sendBuffer.commit(copied);
464 writeBuffer();
465 return static_cast<ssize_t>(length);
466 }
467
468 static ssize_t onSendCallbackStatic(nghttp2_session* session,
469 const uint8_t* data, size_t length,
470 int flags /* flags */, void* userData)
471 {
472 return userPtrToSelf(userData).onSendCallback(session, data, length,
473 flags);
474 }
475
476 void close()
477 {
478 if constexpr (std::is_same_v<Adaptor,
479 boost::beast::ssl_stream<
480 boost::asio::ip::tcp::socket>>)
481 {
482 adaptor.next_layer().close();
483 }
484 else
485 {
486 adaptor.close();
487 }
488 }
489
490 void doRead()
491 {
Ed Tanous62598e32023-07-17 17:06:25 -0700492 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800493 adaptor.async_read_some(
494 inBuffer.prepare(8192),
495 [this, self(shared_from_this())](
496 const boost::system::error_code& ec, size_t bytesTransferred) {
Ed Tanous62598e32023-07-17 17:06:25 -0700497 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
498 bytesTransferred);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800499
500 if (ec)
501 {
Ed Tanous62598e32023-07-17 17:06:25 -0700502 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
503 ec.message());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800504 close();
Ed Tanous62598e32023-07-17 17:06:25 -0700505 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800506 return;
507 }
508 inBuffer.commit(bytesTransferred);
509
510 size_t consumed = 0;
511 for (const auto bufferIt : inBuffer.data())
512 {
513 std::span<const uint8_t> bufferSpan{
514 std::bit_cast<const uint8_t*>(bufferIt.data()),
515 bufferIt.size()};
Ed Tanous62598e32023-07-17 17:06:25 -0700516 BMCWEB_LOG_DEBUG("http2 is getting {} bytes",
517 bufferSpan.size());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800518 ssize_t readLen = ngSession.memRecv(bufferSpan);
519 if (readLen <= 0)
520 {
Ed Tanous62598e32023-07-17 17:06:25 -0700521 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}",
522 readLen);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800523 close();
524 return;
525 }
526 consumed += static_cast<size_t>(readLen);
527 }
528 inBuffer.consume(consumed);
529
530 doRead();
531 });
532 }
533
534 // A mapping from http2 stream ID to Stream Data
535 boost::container::flat_map<int32_t, std::unique_ptr<Http2StreamData>>
536 streams;
537
538 boost::beast::multi_buffer sendBuffer;
539 boost::beast::multi_buffer inBuffer;
540
541 Adaptor adaptor;
542 bool isWriting = false;
543
544 nghttp2_session ngSession;
545
546 Handler* handler;
547 std::function<std::string()>& getCachedDateStr;
548
549 using std::enable_shared_from_this<
550 HTTP2Connection<Adaptor, Handler>>::shared_from_this;
551
552 using std::enable_shared_from_this<
553 HTTP2Connection<Adaptor, Handler>>::weak_from_this;
554};
555} // namespace crow