xref: /openbmc/bmcweb/test/http/http2_connection_test.cpp (revision d2ec1bd7a0267cfd259615b979a4a2119f09a7d6)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #include "async_resp.hpp"
4 #include "http/http2_connection.hpp"
5 #include "http/http_request.hpp"
6 #include "http/http_response.hpp"
7 #include "nghttp2_adapters.hpp"
8 
9 #include <nghttp2/nghttp2.h>
10 #include <unistd.h>
11 
12 #include <boost/asio/buffer.hpp>
13 #include <boost/asio/impl/write.hpp>
14 #include <boost/asio/io_context.hpp>
15 #include <boost/asio/steady_timer.hpp>
16 #include <boost/beast/_experimental/test/stream.hpp>
17 #include <boost/beast/http/field.hpp>
18 
19 #include <bit>
20 #include <cstddef>
21 #include <cstdint>
22 #include <functional>
23 #include <memory>
24 #include <string>
25 #include <string_view>
26 #include <utility>
27 #include <vector>
28 
29 #include "gmock/gmock.h"
30 #include "gtest/gtest.h"
31 namespace crow
32 {
33 
34 namespace
35 {
36 
37 using ::testing::Pair;
38 using ::testing::UnorderedElementsAre;
39 
40 struct FakeHandler
41 {
42     bool called = false;
43     void handle(const std::shared_ptr<Request>& req,
44                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
45     {
46         called = true;
47         EXPECT_EQ(req->url().buffer(), "/redfish/v1/");
48         EXPECT_EQ(req->methodString(), "GET");
49         EXPECT_EQ(req->getHeaderValue(boost::beast::http::field::user_agent),
50                   "curl/8.5.0");
51         EXPECT_EQ(req->getHeaderValue(boost::beast::http::field::accept),
52                   "*/*");
53         EXPECT_EQ(req->getHeaderValue(":authority"), "localhost:18080");
54         asyncResp->res.write("StringOutput");
55     }
56 };
57 
58 std::string getDateStr()
59 {
60     return "TestTime";
61 }
62 
63 void unpackHeaders(std::string_view dataField,
64                    std::vector<std::pair<std::string, std::string>>& headers)
65 {
66     nghttp2_hd_inflater_ex inflater;
67 
68     while (!dataField.empty())
69     {
70         nghttp2_nv nv;
71         int inflateFlags = 0;
72         const uint8_t* data = std::bit_cast<const uint8_t*>(dataField.data());
73         ssize_t parsed =
74             inflater.hd2(&nv, &inflateFlags, data, dataField.size(), 1);
75 
76         ASSERT_GT(parsed, 0);
77         dataField.remove_prefix(static_cast<size_t>(parsed));
78         if ((inflateFlags & NGHTTP2_HD_INFLATE_EMIT) > 0)
79         {
80             const char* namePtr = std::bit_cast<const char*>(nv.name);
81             std::string key(namePtr, nv.namelen);
82             const char* valPtr = std::bit_cast<const char*>(nv.value);
83             std::string value(valPtr, nv.valuelen);
84             headers.emplace_back(key, value);
85         }
86         if ((inflateFlags & NGHTTP2_HD_INFLATE_FINAL) > 0)
87         {
88             EXPECT_EQ(inflater.endHeaders(), 0);
89             break;
90         }
91     }
92     EXPECT_TRUE(dataField.empty());
93 }
94 
95 TEST(http_connection, RequestPropogates)
96 {
97     using namespace std::literals;
98     boost::asio::io_context io;
99     boost::beast::test::stream stream(io);
100     boost::beast::test::stream out(io);
101     stream.connect(out);
102     // This is a binary pre-encrypted stream captured from curl for a request to
103     // curl https://localhost:18080/redfish/v1/
104     std::string_view toSend =
105         // Hello
106         "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
107         // 18 byte settings frame
108         "\x00\x00\x12\x04\x00\x00\x00\x00\x00"
109         // Settings
110         "\x00\x03\x00\x00\x00\x64\x00\x04\x00\xa0\x00\x00\x00\x02\x00\x00\x00\x00"
111         // Window update frame
112         "\x00\x00\x04\x08\x00\x00\x00\x00\x00"
113         // Window update
114         "\x3e\x7f\x00\x01"
115         // Header frame END_STREAM, END_HEADERS set
116         "\x00\x00\x29\x01\x05\x00\x00\x00"
117         // Header payload
118         "\x01\x82\x87\x41\x8b\xa0\xe4\x1d\x13\x9d\x09\xb8\x17\x80\xf0\x3f"
119         "\x04\x89\x62\xc2\xc9\x29\x91\x3b\x1d\xc2\xc7\x7a\x88\x25\xb6\x50"
120         "\xc3\xcb\xb6\xb8\x3f\x53\x03\x2a\x2f\x2a"sv;
121 
122     boost::asio::write(out, boost::asio::buffer(toSend));
123 
124     FakeHandler handler;
125     boost::asio::steady_timer timer(io);
126     std::function<std::string()> date(getDateStr);
127     auto conn = std::make_shared<
128         HTTP2Connection<boost::beast::test::stream, FakeHandler>>(
129         std::move(stream), &handler, date);
130     conn->start();
131 
132     std::string_view expectedPrefix =
133         // Settings frame size 13
134         "\x00\x00\x0c\x04\x00\x00\x00\x00\x00"
135         // 4 max concurrent streams
136         "\x00\x03\x00\x00\x00\x04"
137         // Enable push = false
138         "\x00\x02\x00\x00\x00\x00"
139         // Settings ACK from server to client
140         "\x00\x00\x00\x04\x01\x00\x00\x00\x00"
141 
142         // Start Headers frame stream 1, size 0x005f
143         "\x00\x00\x5f\x01\x04\x00\x00\x00\x01"sv;
144 
145     std::string_view expectedPostfix =
146         // Data Frame, Length 12, Stream 1, End Stream flag set
147         "\x00\x00\x0c\x00\x01\x00\x00\x00\x01"
148         // The body expected
149         "StringOutput"sv;
150 
151     std::string_view outStr;
152     constexpr size_t headerSize = 0x05f;
153 
154     // Run until we receive the expected amount of data
155     while (outStr.size() <
156            expectedPrefix.size() + headerSize + expectedPostfix.size())
157     {
158         io.run_one();
159         outStr = out.str();
160     }
161     EXPECT_TRUE(handler.called);
162 
163     // check the stream output against expected
164     EXPECT_EQ(outStr.substr(0, expectedPrefix.size()), expectedPrefix);
165     outStr.remove_prefix(expectedPrefix.size());
166     std::vector<std::pair<std::string, std::string>> headers;
167     unpackHeaders(outStr.substr(0, headerSize), headers);
168     outStr.remove_prefix(headerSize);
169 
170     EXPECT_THAT(headers,
171                 UnorderedElementsAre(
172                     Pair(":status", "200"), Pair("content-length", "12"),
173                     Pair("strict-transport-security",
174                          "max-age=31536000; includeSubdomains"),
175                     Pair("cache-control", "no-store, max-age=0"),
176                     Pair("x-content-type-options", "nosniff"),
177                     Pair("pragma", "no-cache"), Pair("date", "TestTime")));
178 
179     EXPECT_EQ(outStr, expectedPostfix);
180 }
181 
182 } // namespace
183 } // namespace crow
184