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