xref: /openbmc/bmcweb/http/http2_connection.hpp (revision e7245fe8)
1 #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 
34 namespace crow
35 {
36 
37 struct Http2StreamData
38 {
39     crow::Request req{};
40     crow::Response res{};
41     size_t sentSofar = 0;
42 };
43 
44 template <typename Adaptor, typename Handler>
45 class 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         {
69             BMCWEB_LOG_ERROR("send_server_connection_header failed");
70             return;
71         }
72         doRead();
73     }
74 
75     int sendServerConnectionHeader()
76     {
77         BMCWEB_LOG_DEBUG("send_server_connection_header()");
78 
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         {
86             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
87             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         {
100             BMCWEB_LOG_DEBUG("Source was null???");
101             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
102         }
103 
104         BMCWEB_LOG_DEBUG("File read callback length: {}", length);
105         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
106         Http2StreamData* str = reinterpret_cast<Http2StreamData*>(source->ptr);
107         crow::Response& res = str->res;
108 
109         BMCWEB_LOG_DEBUG("total: {} send_sofar: {}", res.body().size(),
110                          str->sentSofar);
111 
112         size_t toSend = std::min(res.body().size() - str->sentSofar, length);
113         BMCWEB_LOG_DEBUG("Copying {} bytes to buf", toSend);
114 
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         {
123             BMCWEB_LOG_DEBUG("Setting OEF flag");
124             *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     {
141         BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
142 
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         {
178             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
179             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     {
204         BMCWEB_LOG_DEBUG("on_request_recv");
205 
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;
214         BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
215                          thisReq.url().encoded_path());
216 
217         crow::Response& thisRes = it->second->res;
218 
219         thisRes.setCompleteRequestHandler(
220             [this, streamId](Response& completeRes) {
221             BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
222             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     {
237         BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
238         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     {
258         BMCWEB_LOG_DEBUG("on_frame_recv_callback");
259         if (userData == nullptr)
260         {
261             BMCWEB_LOG_CRITICAL("user data was null?");
262             return NGHTTP2_ERR_CALLBACK_FAILURE;
263         }
264         if (frame == nullptr)
265         {
266             BMCWEB_LOG_CRITICAL("frame was null?");
267             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     {
284         BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
285         if (userData == nullptr)
286         {
287             BMCWEB_LOG_CRITICAL("user data was null?");
288             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 
311         BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
312                          valueSv);
313 
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                 {
324                     BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
325                     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                     {
341                         BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
342                         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         {
368             BMCWEB_LOG_CRITICAL("user data was null?");
369             return NGHTTP2_ERR_CALLBACK_FAILURE;
370         }
371         if (frame == nullptr)
372         {
373             BMCWEB_LOG_CRITICAL("frame was null?");
374             return NGHTTP2_ERR_CALLBACK_FAILURE;
375         }
376         if (name == nullptr)
377         {
378             BMCWEB_LOG_CRITICAL("name was null?");
379             return NGHTTP2_ERR_CALLBACK_FAILURE;
380         }
381         if (value == nullptr)
382         {
383             BMCWEB_LOG_CRITICAL("value was null?");
384             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         {
395             BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
396 
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     {
412         BMCWEB_LOG_DEBUG("on_begin_headers_callback");
413         if (userData == nullptr)
414         {
415             BMCWEB_LOG_CRITICAL("user data was null?");
416             return NGHTTP2_ERR_CALLBACK_FAILURE;
417         }
418         if (frame == nullptr)
419         {
420             BMCWEB_LOG_CRITICAL("frame was null?");
421             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;
431         BMCWEB_LOG_DEBUG("Sent {}", sendLength);
432         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     {
460         BMCWEB_LOG_DEBUG("On send callback size={}", length);
461         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     {
492         BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
493         adaptor.async_read_some(
494             inBuffer.prepare(8192),
495             [this, self(shared_from_this())](
496                 const boost::system::error_code& ec, size_t bytesTransferred) {
497             BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
498                              bytesTransferred);
499 
500             if (ec)
501             {
502                 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
503                                  ec.message());
504                 close();
505                 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
506                 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()};
516                 BMCWEB_LOG_DEBUG("http2 is getting {} bytes",
517                                  bufferSpan.size());
518                 ssize_t readLen = ngSession.memRecv(bufferSpan);
519                 if (readLen <= 0)
520                 {
521                     BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}",
522                                      readLen);
523                     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
556