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(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 56 std::string getDateStr() 57 { 58 return "TestTime"; 59 } 60 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 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