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