xref: /openbmc/bmcweb/http/http2_connection.hpp (revision e610b3168321eee167271e4532c37fe1ed9c6f56)
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/asio/io_context.hpp>
16 #include <boost/asio/ip/tcp.hpp>
17 #include <boost/asio/ssl/stream.hpp>
18 #include <boost/asio/steady_timer.hpp>
19 #include <boost/beast/http/error.hpp>
20 #include <boost/beast/http/parser.hpp>
21 #include <boost/beast/http/read.hpp>
22 #include <boost/beast/http/serializer.hpp>
23 #include <boost/beast/http/write.hpp>
24 #include <boost/beast/ssl/ssl_stream.hpp>
25 #include <boost/beast/websocket.hpp>
26 #include <boost/system/error_code.hpp>
27 
28 #include <array>
29 #include <atomic>
30 #include <chrono>
31 #include <functional>
32 #include <memory>
33 #include <vector>
34 
35 namespace crow
36 {
37 
38 struct Http2StreamData
39 {
40     Request req{};
41     Response res{};
42     std::optional<bmcweb::FileBody::writer> writer;
43 };
44 
45 template <typename Adaptor, typename Handler>
46 class HTTP2Connection :
47     public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
48 {
49     using self_type = HTTP2Connection<Adaptor, Handler>;
50 
51   public:
52     HTTP2Connection(Adaptor&& adaptorIn, Handler* handlerIn,
53                     std::function<std::string()>& getCachedDateStrF) :
54         adaptor(std::move(adaptorIn)),
55         ngSession(initializeNghttp2Session()), handler(handlerIn),
56         getCachedDateStr(getCachedDateStrF)
57     {}
58 
59     void start()
60     {
61         // Create the control stream
62         streams[0];
63 
64         if (sendServerConnectionHeader() != 0)
65         {
66             BMCWEB_LOG_ERROR("send_server_connection_header failed");
67             return;
68         }
69         doRead();
70     }
71 
72     int sendServerConnectionHeader()
73     {
74         BMCWEB_LOG_DEBUG("send_server_connection_header()");
75 
76         uint32_t maxStreams = 4;
77         std::array<nghttp2_settings_entry, 2> iv = {
78             {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
79              {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
80         int rv = ngSession.submitSettings(iv);
81         if (rv != 0)
82         {
83             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
84             return -1;
85         }
86         writeBuffer();
87         return 0;
88     }
89 
90     static ssize_t fileReadCallback(nghttp2_session* /* session */,
91                                     int32_t streamId, uint8_t* buf,
92                                     size_t length, uint32_t* dataFlags,
93                                     nghttp2_data_source* /*source*/,
94                                     void* userPtr)
95     {
96         self_type& self = userPtrToSelf(userPtr);
97 
98         auto streamIt = self.streams.find(streamId);
99         if (streamIt == self.streams.end())
100         {
101             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
102         }
103         Http2StreamData& stream = streamIt->second;
104         BMCWEB_LOG_DEBUG("File read callback length: {}", length);
105 
106         boost::beast::error_code ec;
107         boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
108             stream.writer->getWithMaxSize(ec, length);
109         if (ec)
110         {
111             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
112         }
113 
114         if (!out)
115         {
116             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
117             return 0;
118         }
119 
120         BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
121         if (length < out->first.size())
122         {
123             // Should never happen because of length limit on get() above
124             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
125         }
126 
127         BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
128         memcpy(buf, out->first.data(), out->first.size());
129 
130         if (!out->second)
131         {
132             BMCWEB_LOG_DEBUG("Setting OEF and nocopy flag");
133             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
134             //*dataFlags |= NGHTTP2_DATA_FLAG_NO_COPY;
135         }
136         return static_cast<ssize_t>(out->first.size());
137     }
138 
139     nghttp2_nv headerFromStringViews(std::string_view name,
140                                      std::string_view value, uint8_t flags)
141     {
142         uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
143         uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
144         return {nameData, valueData, name.size(), value.size(), flags};
145     }
146 
147     int sendResponse(Response& completedRes, int32_t streamId)
148     {
149         BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
150 
151         auto it = streams.find(streamId);
152         if (it == streams.end())
153         {
154             close();
155             return -1;
156         }
157         Response& thisRes = it->second.res;
158         thisRes = std::move(completedRes);
159         crow::Request& thisReq = it->second.req;
160         std::vector<nghttp2_nv> hdr;
161 
162         completeResponseFields(thisReq, thisRes);
163         thisRes.addHeader(boost::beast::http::field::date, getCachedDateStr());
164 
165         boost::beast::http::fields& fields = thisRes.fields();
166         std::string code = std::to_string(thisRes.resultInt());
167         hdr.emplace_back(
168             headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
169         for (const boost::beast::http::fields::value_type& header : fields)
170         {
171             hdr.emplace_back(headerFromStringViews(
172                 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
173         }
174         Http2StreamData& stream = it->second;
175         crow::Response& res = stream.res;
176         http::response<bmcweb::FileBody>& fbody = res.response;
177         stream.writer.emplace(fbody.base(), fbody.body());
178 
179         nghttp2_data_provider dataPrd{
180             .source = {.fd = 0},
181             .read_callback = fileReadCallback,
182         };
183 
184         int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
185         if (rv != 0)
186         {
187             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
188             close();
189             return -1;
190         }
191         writeBuffer();
192 
193         return 0;
194     }
195 
196     nghttp2_session initializeNghttp2Session()
197     {
198         nghttp2_session_callbacks callbacks;
199         callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
200         callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
201         callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
202         callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
203 
204         nghttp2_session session(callbacks);
205         session.setUserData(this);
206 
207         return session;
208     }
209 
210     int onRequestRecv(int32_t streamId)
211     {
212         BMCWEB_LOG_DEBUG("on_request_recv");
213 
214         auto it = streams.find(streamId);
215         if (it == streams.end())
216         {
217             close();
218             return -1;
219         }
220 
221         crow::Request& thisReq = it->second.req;
222         BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
223                          thisReq.url().encoded_path());
224 
225         crow::Response& thisRes = it->second.res;
226 
227         thisRes.setCompleteRequestHandler(
228             [this, streamId](Response& completeRes) {
229             BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
230             if (sendResponse(completeRes, streamId) != 0)
231             {
232                 close();
233                 return;
234             }
235         });
236         auto asyncResp =
237             std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
238         handler->handle(thisReq, asyncResp);
239 
240         return 0;
241     }
242 
243     int onFrameRecvCallback(const nghttp2_frame& frame)
244     {
245         BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
246         switch (frame.hd.type)
247         {
248             case NGHTTP2_DATA:
249             case NGHTTP2_HEADERS:
250                 // Check that the client request has finished
251                 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
252                 {
253                     return onRequestRecv(frame.hd.stream_id);
254                 }
255                 break;
256             default:
257                 break;
258         }
259         return 0;
260     }
261 
262     static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
263                                          const nghttp2_frame* frame,
264                                          void* userData)
265     {
266         BMCWEB_LOG_DEBUG("on_frame_recv_callback");
267         if (userData == nullptr)
268         {
269             BMCWEB_LOG_CRITICAL("user data was null?");
270             return NGHTTP2_ERR_CALLBACK_FAILURE;
271         }
272         if (frame == nullptr)
273         {
274             BMCWEB_LOG_CRITICAL("frame was null?");
275             return NGHTTP2_ERR_CALLBACK_FAILURE;
276         }
277         return userPtrToSelf(userData).onFrameRecvCallback(*frame);
278     }
279 
280     static self_type& userPtrToSelf(void* userData)
281     {
282         // This method exists to keep the unsafe reinterpret cast in one
283         // place.
284         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
285         return *reinterpret_cast<self_type*>(userData);
286     }
287 
288     static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
289                                            int32_t streamId,
290                                            uint32_t /*unused*/, void* userData)
291     {
292         BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
293         if (userData == nullptr)
294         {
295             BMCWEB_LOG_CRITICAL("user data was null?");
296             return NGHTTP2_ERR_CALLBACK_FAILURE;
297         }
298         if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
299         {
300             return -1;
301         }
302         return 0;
303     }
304 
305     int onHeaderCallback(const nghttp2_frame& frame,
306                          std::span<const uint8_t> name,
307                          std::span<const uint8_t> value)
308     {
309         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
310         std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
311                                 name.size());
312         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
313         std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
314                                  value.size());
315 
316         BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
317                          valueSv);
318 
319         switch (frame.hd.type)
320         {
321             case NGHTTP2_HEADERS:
322                 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
323                 {
324                     break;
325                 }
326                 auto thisStream = streams.find(frame.hd.stream_id);
327                 if (thisStream == streams.end())
328                 {
329                     BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
330                     close();
331                     return -1;
332                 }
333 
334                 crow::Request& thisReq = thisStream->second.req;
335 
336                 if (nameSv == ":path")
337                 {
338                     thisReq.target(valueSv);
339                 }
340                 else if (nameSv == ":method")
341                 {
342                     boost::beast::http::verb verb =
343                         boost::beast::http::string_to_verb(valueSv);
344                     if (verb == boost::beast::http::verb::unknown)
345                     {
346                         BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
347                         close();
348                         return -1;
349                     }
350                     thisReq.req.method(verb);
351                 }
352                 else if (nameSv == ":scheme")
353                 {
354                     // Nothing to check on scheme
355                 }
356                 else
357                 {
358                     thisReq.req.set(nameSv, valueSv);
359                 }
360                 break;
361         }
362         return 0;
363     }
364 
365     static int onHeaderCallbackStatic(nghttp2_session* /* session */,
366                                       const nghttp2_frame* frame,
367                                       const uint8_t* name, size_t namelen,
368                                       const uint8_t* value, size_t vallen,
369                                       uint8_t /* flags */, void* userData)
370     {
371         if (userData == nullptr)
372         {
373             BMCWEB_LOG_CRITICAL("user data was null?");
374             return NGHTTP2_ERR_CALLBACK_FAILURE;
375         }
376         if (frame == nullptr)
377         {
378             BMCWEB_LOG_CRITICAL("frame was null?");
379             return NGHTTP2_ERR_CALLBACK_FAILURE;
380         }
381         if (name == nullptr)
382         {
383             BMCWEB_LOG_CRITICAL("name was null?");
384             return NGHTTP2_ERR_CALLBACK_FAILURE;
385         }
386         if (value == nullptr)
387         {
388             BMCWEB_LOG_CRITICAL("value was null?");
389             return NGHTTP2_ERR_CALLBACK_FAILURE;
390         }
391         return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
392                                                         {value, vallen});
393     }
394 
395     int onBeginHeadersCallback(const nghttp2_frame& frame)
396     {
397         if (frame.hd.type == NGHTTP2_HEADERS &&
398             frame.headers.cat == NGHTTP2_HCAT_REQUEST)
399         {
400             BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
401 
402             Http2StreamData& stream = streams[frame.hd.stream_id];
403             // http2 is by definition always tls
404             stream.req.isSecure = true;
405         }
406         return 0;
407     }
408 
409     static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
410                                             const nghttp2_frame* frame,
411                                             void* userData)
412     {
413         BMCWEB_LOG_DEBUG("on_begin_headers_callback");
414         if (userData == nullptr)
415         {
416             BMCWEB_LOG_CRITICAL("user data was null?");
417             return NGHTTP2_ERR_CALLBACK_FAILURE;
418         }
419         if (frame == nullptr)
420         {
421             BMCWEB_LOG_CRITICAL("frame was null?");
422             return NGHTTP2_ERR_CALLBACK_FAILURE;
423         }
424         return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
425     }
426 
427     static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
428                                  const boost::system::error_code& ec,
429                                  size_t sendLength)
430     {
431         self->isWriting = false;
432         BMCWEB_LOG_DEBUG("Sent {}", sendLength);
433         if (ec)
434         {
435             self->close();
436             return;
437         }
438         self->writeBuffer();
439     }
440 
441     void writeBuffer()
442     {
443         if (isWriting)
444         {
445             return;
446         }
447         std::span<const uint8_t> data = ngSession.memSend();
448         if (data.empty())
449         {
450             return;
451         }
452         isWriting = true;
453         adaptor.async_write_some(
454             boost::asio::buffer(data.data(), data.size()),
455             std::bind_front(afterWriteBuffer, shared_from_this()));
456     }
457 
458     void close()
459     {
460         if constexpr (std::is_same_v<Adaptor,
461                                      boost::beast::ssl_stream<
462                                          boost::asio::ip::tcp::socket>>)
463         {
464             adaptor.next_layer().close();
465         }
466         else
467         {
468             adaptor.close();
469         }
470     }
471 
472     void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
473                      const boost::system::error_code& ec,
474                      size_t bytesTransferred)
475     {
476         BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
477                          bytesTransferred);
478 
479         if (ec)
480         {
481             BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
482                              ec.message());
483             close();
484             BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
485             return;
486         }
487         std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
488 
489         ssize_t readLen = ngSession.memRecv(bufferSpan);
490         if (readLen < 0)
491         {
492             BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
493             close();
494             return;
495         }
496         writeBuffer();
497 
498         doRead();
499     }
500 
501     void doRead()
502     {
503         BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
504         adaptor.async_read_some(
505             boost::asio::buffer(inBuffer),
506             std::bind_front(&self_type::afterDoRead, this, shared_from_this()));
507     }
508 
509     // A mapping from http2 stream ID to Stream Data
510     std::map<int32_t, Http2StreamData> streams;
511 
512     std::array<uint8_t, 8192> inBuffer{};
513 
514     Adaptor adaptor;
515     bool isWriting = false;
516 
517     nghttp2_session ngSession;
518 
519     Handler* handler;
520     std::function<std::string()>& getCachedDateStr;
521 
522     using std::enable_shared_from_this<
523         HTTP2Connection<Adaptor, Handler>>::shared_from_this;
524 
525     using std::enable_shared_from_this<
526         HTTP2Connection<Adaptor, Handler>>::weak_from_this;
527 };
528 } // namespace crow
529