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