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