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