xref: /openbmc/bmcweb/http/http2_connection.hpp (revision 27b0cf90)
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[0];
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 streamId, uint8_t* buf,
94                                     size_t length, uint32_t* dataFlags,
95                                     nghttp2_data_source* /*source*/,
96                                     void* userPtr)
97     {
98         self_type& self = userPtrToSelf(userPtr);
99 
100         auto streamIt = self.streams.find(streamId);
101         if (streamIt == self.streams.end())
102         {
103             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
104         }
105         Http2StreamData& stream = streamIt->second;
106         BMCWEB_LOG_DEBUG("File read callback length: {}", length);
107         crow::Response& res = stream.res;
108 
109         Response::string_response* body =
110             boost::variant2::get_if<Response::string_response>(&res.response);
111         Response::file_response* fbody =
112             boost::variant2::get_if<Response::file_response>(&res.response);
113 
114         size_t size = res.size();
115         BMCWEB_LOG_DEBUG("total: {} send_sofar: {}", size, stream.sentSofar);
116 
117         size_t toSend = std::min(size - stream.sentSofar, length);
118         BMCWEB_LOG_DEBUG("Copying {} bytes to buf", toSend);
119 
120         if (body != nullptr)
121         {
122             std::string::const_iterator bodyBegin = body->body().begin();
123             std::advance(bodyBegin, stream.sentSofar);
124 
125             memcpy(buf, &*bodyBegin, toSend);
126         }
127         else if (fbody != nullptr)
128         {
129             boost::system::error_code ec;
130 
131             size_t nread = fbody->body().file().read(buf, toSend, ec);
132             if (ec || nread != toSend)
133             {
134                 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
135             }
136         }
137         else
138         {
139             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
140         }
141 
142         stream.sentSofar += toSend;
143 
144         if (stream.sentSofar >= size)
145         {
146             BMCWEB_LOG_DEBUG("Setting OEF flag");
147             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
148             //*dataFlags |= NGHTTP2_DATA_FLAG_NO_COPY;
149         }
150         return static_cast<ssize_t>(toSend);
151     }
152 
153     nghttp2_nv headerFromStringViews(std::string_view name,
154                                      std::string_view value)
155     {
156         uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
157         uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
158         return {nameData, valueData, name.size(), value.size(),
159                 NGHTTP2_NV_FLAG_NONE};
160     }
161 
162     int sendResponse(Response& completedRes, int32_t streamId)
163     {
164         BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
165 
166         auto it = streams.find(streamId);
167         if (it == streams.end())
168         {
169             close();
170             return -1;
171         }
172         Response& thisRes = it->second.res;
173         thisRes = std::move(completedRes);
174         crow::Request& thisReq = it->second.req;
175         std::vector<nghttp2_nv> hdr;
176 
177         completeResponseFields(thisReq, thisRes);
178         thisRes.addHeader(boost::beast::http::field::date, getCachedDateStr());
179 
180         boost::beast::http::fields& fields = thisRes.fields();
181         std::string code = std::to_string(thisRes.resultInt());
182         hdr.emplace_back(headerFromStringViews(":status", code));
183         for (const boost::beast::http::fields::value_type& header : fields)
184         {
185             hdr.emplace_back(
186                 headerFromStringViews(header.name_string(), header.value()));
187         }
188         Http2StreamData& stream = it->second;
189         stream.sentSofar = 0;
190 
191         nghttp2_data_provider dataPrd{
192             .source = {.fd = 0},
193             .read_callback = fileReadCallback,
194         };
195 
196         int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
197         if (rv != 0)
198         {
199             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
200             close();
201             return -1;
202         }
203         ngSession.send();
204 
205         return 0;
206     }
207 
208     nghttp2_session initializeNghttp2Session()
209     {
210         nghttp2_session_callbacks callbacks;
211         callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
212         callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
213         callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
214         callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
215         callbacks.setSendCallback(onSendCallbackStatic);
216 
217         nghttp2_session session(callbacks);
218         session.setUserData(this);
219 
220         return session;
221     }
222 
223     int onRequestRecv(int32_t streamId)
224     {
225         BMCWEB_LOG_DEBUG("on_request_recv");
226 
227         auto it = streams.find(streamId);
228         if (it == streams.end())
229         {
230             close();
231             return -1;
232         }
233 
234         crow::Request& thisReq = it->second.req;
235         BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
236                          thisReq.url().encoded_path());
237 
238         crow::Response& thisRes = it->second.res;
239 
240         thisRes.setCompleteRequestHandler(
241             [this, streamId](Response& completeRes) {
242             BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
243             if (sendResponse(completeRes, streamId) != 0)
244             {
245                 close();
246                 return;
247             }
248         });
249         auto asyncResp =
250             std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
251         handler->handle(thisReq, asyncResp);
252 
253         return 0;
254     }
255 
256     int onFrameRecvCallback(const nghttp2_frame& frame)
257     {
258         BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
259         switch (frame.hd.type)
260         {
261             case NGHTTP2_DATA:
262             case NGHTTP2_HEADERS:
263                 // Check that the client request has finished
264                 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
265                 {
266                     return onRequestRecv(frame.hd.stream_id);
267                 }
268                 break;
269             default:
270                 break;
271         }
272         return 0;
273     }
274 
275     static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
276                                          const nghttp2_frame* frame,
277                                          void* userData)
278     {
279         BMCWEB_LOG_DEBUG("on_frame_recv_callback");
280         if (userData == nullptr)
281         {
282             BMCWEB_LOG_CRITICAL("user data was null?");
283             return NGHTTP2_ERR_CALLBACK_FAILURE;
284         }
285         if (frame == nullptr)
286         {
287             BMCWEB_LOG_CRITICAL("frame was null?");
288             return NGHTTP2_ERR_CALLBACK_FAILURE;
289         }
290         return userPtrToSelf(userData).onFrameRecvCallback(*frame);
291     }
292 
293     static self_type& userPtrToSelf(void* userData)
294     {
295         // This method exists to keep the unsafe reinterpret cast in one
296         // place.
297         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
298         return *reinterpret_cast<self_type*>(userData);
299     }
300 
301     static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
302                                            int32_t streamId,
303                                            uint32_t /*unused*/, void* userData)
304     {
305         BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
306         if (userData == nullptr)
307         {
308             BMCWEB_LOG_CRITICAL("user data was null?");
309             return NGHTTP2_ERR_CALLBACK_FAILURE;
310         }
311         if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
312         {
313             return -1;
314         }
315         return 0;
316     }
317 
318     int onHeaderCallback(const nghttp2_frame& frame,
319                          std::span<const uint8_t> name,
320                          std::span<const uint8_t> value)
321     {
322         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
323         std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
324                                 name.size());
325         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
326         std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
327                                  value.size());
328 
329         BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
330                          valueSv);
331 
332         switch (frame.hd.type)
333         {
334             case NGHTTP2_HEADERS:
335                 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
336                 {
337                     break;
338                 }
339                 auto thisStream = streams.find(frame.hd.stream_id);
340                 if (thisStream == streams.end())
341                 {
342                     BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
343                     close();
344                     return -1;
345                 }
346 
347                 crow::Request& thisReq = thisStream->second.req;
348 
349                 if (nameSv == ":path")
350                 {
351                     thisReq.target(valueSv);
352                 }
353                 else if (nameSv == ":method")
354                 {
355                     boost::beast::http::verb verb =
356                         boost::beast::http::string_to_verb(valueSv);
357                     if (verb == boost::beast::http::verb::unknown)
358                     {
359                         BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
360                         close();
361                         return -1;
362                     }
363                     thisReq.req.method(verb);
364                 }
365                 else if (nameSv == ":scheme")
366                 {
367                     // Nothing to check on scheme
368                 }
369                 else
370                 {
371                     thisReq.req.set(nameSv, valueSv);
372                 }
373                 break;
374         }
375         return 0;
376     }
377 
378     static int onHeaderCallbackStatic(nghttp2_session* /* session */,
379                                       const nghttp2_frame* frame,
380                                       const uint8_t* name, size_t namelen,
381                                       const uint8_t* value, size_t vallen,
382                                       uint8_t /* flags */, void* userData)
383     {
384         if (userData == nullptr)
385         {
386             BMCWEB_LOG_CRITICAL("user data was null?");
387             return NGHTTP2_ERR_CALLBACK_FAILURE;
388         }
389         if (frame == nullptr)
390         {
391             BMCWEB_LOG_CRITICAL("frame was null?");
392             return NGHTTP2_ERR_CALLBACK_FAILURE;
393         }
394         if (name == nullptr)
395         {
396             BMCWEB_LOG_CRITICAL("name was null?");
397             return NGHTTP2_ERR_CALLBACK_FAILURE;
398         }
399         if (value == nullptr)
400         {
401             BMCWEB_LOG_CRITICAL("value was null?");
402             return NGHTTP2_ERR_CALLBACK_FAILURE;
403         }
404         return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
405                                                         {value, vallen});
406     }
407 
408     int onBeginHeadersCallback(const nghttp2_frame& frame)
409     {
410         if (frame.hd.type == NGHTTP2_HEADERS &&
411             frame.headers.cat == NGHTTP2_HCAT_REQUEST)
412         {
413             BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
414 
415             Http2StreamData& stream = streams[frame.hd.stream_id];
416             // http2 is by definition always tls
417             stream.req.isSecure = true;
418         }
419         return 0;
420     }
421 
422     static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
423                                             const nghttp2_frame* frame,
424                                             void* userData)
425     {
426         BMCWEB_LOG_DEBUG("on_begin_headers_callback");
427         if (userData == nullptr)
428         {
429             BMCWEB_LOG_CRITICAL("user data was null?");
430             return NGHTTP2_ERR_CALLBACK_FAILURE;
431         }
432         if (frame == nullptr)
433         {
434             BMCWEB_LOG_CRITICAL("frame was null?");
435             return NGHTTP2_ERR_CALLBACK_FAILURE;
436         }
437         return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
438     }
439 
440     static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
441                                  const boost::system::error_code& ec,
442                                  size_t sendLength)
443     {
444         self->isWriting = false;
445         BMCWEB_LOG_DEBUG("Sent {}", sendLength);
446         if (ec)
447         {
448             self->close();
449             return;
450         }
451         self->sendBuffer.consume(sendLength);
452         self->writeBuffer();
453     }
454 
455     void writeBuffer()
456     {
457         if (isWriting)
458         {
459             return;
460         }
461         if (sendBuffer.size() <= 0)
462         {
463             return;
464         }
465         isWriting = true;
466         adaptor.async_write_some(
467             sendBuffer.data(),
468             std::bind_front(afterWriteBuffer, shared_from_this()));
469     }
470 
471     ssize_t onSendCallback(nghttp2_session* /*session */, const uint8_t* data,
472                            size_t length, int /* flags */)
473     {
474         BMCWEB_LOG_DEBUG("On send callback size={}", length);
475         size_t copied = boost::asio::buffer_copy(
476             sendBuffer.prepare(length), boost::asio::buffer(data, length));
477         sendBuffer.commit(copied);
478         writeBuffer();
479         return static_cast<ssize_t>(length);
480     }
481 
482     static ssize_t onSendCallbackStatic(nghttp2_session* session,
483                                         const uint8_t* data, size_t length,
484                                         int flags /* flags */, void* userData)
485     {
486         return userPtrToSelf(userData).onSendCallback(session, data, length,
487                                                       flags);
488     }
489 
490     void close()
491     {
492         if constexpr (std::is_same_v<Adaptor,
493                                      boost::beast::ssl_stream<
494                                          boost::asio::ip::tcp::socket>>)
495         {
496             adaptor.next_layer().close();
497         }
498         else
499         {
500             adaptor.close();
501         }
502     }
503 
504     void doRead()
505     {
506         BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
507         adaptor.async_read_some(
508             inBuffer.prepare(8192),
509             [this, self(shared_from_this())](
510                 const boost::system::error_code& ec, size_t bytesTransferred) {
511             BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
512                              bytesTransferred);
513 
514             if (ec)
515             {
516                 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
517                                  ec.message());
518                 close();
519                 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
520                 return;
521             }
522             inBuffer.commit(bytesTransferred);
523 
524             size_t consumed = 0;
525             for (const auto bufferIt : inBuffer.data())
526             {
527                 std::span<const uint8_t> bufferSpan{
528                     std::bit_cast<const uint8_t*>(bufferIt.data()),
529                     bufferIt.size()};
530                 BMCWEB_LOG_DEBUG("http2 is getting {} bytes",
531                                  bufferSpan.size());
532                 ssize_t readLen = ngSession.memRecv(bufferSpan);
533                 if (readLen <= 0)
534                 {
535                     BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}",
536                                      readLen);
537                     close();
538                     return;
539                 }
540                 consumed += static_cast<size_t>(readLen);
541             }
542             inBuffer.consume(consumed);
543 
544             doRead();
545         });
546     }
547 
548     // A mapping from http2 stream ID to Stream Data
549     boost::container::flat_map<int32_t, Http2StreamData> streams;
550 
551     boost::beast::multi_buffer sendBuffer;
552     boost::beast::multi_buffer inBuffer;
553 
554     Adaptor adaptor;
555     bool isWriting = false;
556 
557     nghttp2_session ngSession;
558 
559     Handler* handler;
560     std::function<std::string()>& getCachedDateStr;
561 
562     using std::enable_shared_from_this<
563         HTTP2Connection<Adaptor, Handler>>::shared_from_this;
564 
565     using std::enable_shared_from_this<
566         HTTP2Connection<Adaptor, Handler>>::weak_from_this;
567 };
568 } // namespace crow
569