xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/http2/adapter/nghttp2_test.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 #include "quiche/http2/adapter/nghttp2.h"
2 
3 #include "absl/strings/str_cat.h"
4 #include "quiche/http2/adapter/mock_nghttp2_callbacks.h"
5 #include "quiche/http2/adapter/nghttp2_test_utils.h"
6 #include "quiche/http2/adapter/nghttp2_util.h"
7 #include "quiche/http2/adapter/test_frame_sequence.h"
8 #include "quiche/http2/adapter/test_utils.h"
9 #include "quiche/common/platform/api/quiche_test.h"
10 
11 namespace http2 {
12 namespace adapter {
13 namespace test {
14 namespace {
15 
16 using testing::_;
17 
18 enum FrameType {
19   DATA,
20   HEADERS,
21   PRIORITY,
22   RST_STREAM,
23   SETTINGS,
24   PUSH_PROMISE,
25   PING,
26   GOAWAY,
27   WINDOW_UPDATE,
28 };
29 
GetOptions()30 nghttp2_option* GetOptions() {
31   nghttp2_option* options;
32   nghttp2_option_new(&options);
33   // Set some common options for compatibility.
34   nghttp2_option_set_no_closed_streams(options, 1);
35   nghttp2_option_set_no_auto_window_update(options, 1);
36   nghttp2_option_set_max_send_header_block_length(options, 0x2000000);
37   nghttp2_option_set_max_outbound_ack(options, 10000);
38   return options;
39 }
40 
41 class Nghttp2Test : public quiche::test::QuicheTest {
42  public:
Nghttp2Test()43   Nghttp2Test() : session_(MakeSessionPtr(nullptr)) {}
44 
SetUp()45   void SetUp() override { InitializeSession(); }
46 
47   virtual Perspective GetPerspective() = 0;
48 
InitializeSession()49   void InitializeSession() {
50     auto nghttp2_callbacks = MockNghttp2Callbacks::GetCallbacks();
51     nghttp2_option* options = GetOptions();
52     nghttp2_session* ptr;
53     if (GetPerspective() == Perspective::kClient) {
54       nghttp2_session_client_new2(&ptr, nghttp2_callbacks.get(),
55                                   &mock_callbacks_, options);
56     } else {
57       nghttp2_session_server_new2(&ptr, nghttp2_callbacks.get(),
58                                   &mock_callbacks_, options);
59     }
60     nghttp2_option_del(options);
61 
62     // Sets up the Send() callback to append to |serialized_|.
63     EXPECT_CALL(mock_callbacks_, Send(_, _, _))
64         .WillRepeatedly(
65             [this](const uint8_t* data, size_t length, int /*flags*/) {
66               absl::StrAppend(&serialized_, ToStringView(data, length));
67               return length;
68             });
69     // Sets up the SendData() callback to fetch and append data from a
70     // TestDataSource.
71     EXPECT_CALL(mock_callbacks_, SendData(_, _, _, _))
72         .WillRepeatedly([this](nghttp2_frame* /*frame*/, const uint8_t* framehd,
73                                size_t length, nghttp2_data_source* source) {
74           QUICHE_LOG(INFO) << "Appending frame header and " << length
75                            << " bytes of data";
76           auto* s = static_cast<TestDataSource*>(source->ptr);
77           absl::StrAppend(&serialized_, ToStringView(framehd, 9),
78                           s->ReadNext(length));
79           return 0;
80         });
81     session_ = MakeSessionPtr(ptr);
82   }
83 
84   testing::StrictMock<MockNghttp2Callbacks> mock_callbacks_;
85   nghttp2_session_unique_ptr session_;
86   std::string serialized_;
87 };
88 
89 class Nghttp2ClientTest : public Nghttp2Test {
90  public:
GetPerspective()91   Perspective GetPerspective() override { return Perspective::kClient; }
92 };
93 
94 // Verifies nghttp2 behavior when acting as a client.
TEST_F(Nghttp2ClientTest,ClientReceivesUnexpectedHeaders)95 TEST_F(Nghttp2ClientTest, ClientReceivesUnexpectedHeaders) {
96   const std::string initial_frames = TestFrameSequence()
97                                          .ServerPreface()
98                                          .Ping(42)
99                                          .WindowUpdate(0, 1000)
100                                          .Serialize();
101 
102   testing::InSequence seq;
103   EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
104   EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));
105   EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, PING, 0)));
106   EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsPing(42)));
107   EXPECT_CALL(mock_callbacks_,
108               OnBeginFrame(HasFrameHeader(0, WINDOW_UPDATE, 0)));
109   EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsWindowUpdate(1000)));
110 
111   ssize_t result = nghttp2_session_mem_recv(
112       session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
113   ASSERT_EQ(result, initial_frames.size());
114 
115   const std::string unexpected_stream_frames =
116       TestFrameSequence()
117           .Headers(1,
118                    {{":status", "200"},
119                     {"server", "my-fake-server"},
120                     {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}},
121                    /*fin=*/false)
122           .Data(1, "This is the response body.")
123           .RstStream(3, Http2ErrorCode::INTERNAL_ERROR)
124           .GoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!")
125           .Serialize();
126 
127   EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(1, HEADERS, _)));
128   EXPECT_CALL(mock_callbacks_, OnInvalidFrameRecv(IsHeaders(1, _, _), _));
129   // No events from the DATA, RST_STREAM or GOAWAY.
130 
131   nghttp2_session_mem_recv(session_.get(),
132                            ToUint8Ptr(unexpected_stream_frames.data()),
133                            unexpected_stream_frames.size());
134 }
135 
136 // Tests the request-sending behavior of nghttp2 when acting as a client.
TEST_F(Nghttp2ClientTest,ClientSendsRequest)137 TEST_F(Nghttp2ClientTest, ClientSendsRequest) {
138   int result = nghttp2_session_send(session_.get());
139   ASSERT_EQ(result, 0);
140 
141   EXPECT_THAT(serialized_, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
142   serialized_.clear();
143 
144   const std::string initial_frames =
145       TestFrameSequence().ServerPreface().Serialize();
146   testing::InSequence s;
147 
148   // Server preface (empty SETTINGS)
149   EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
150   EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));
151 
152   ssize_t recv_result = nghttp2_session_mem_recv(
153       session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
154   EXPECT_EQ(initial_frames.size(), recv_result);
155 
156   // Client wants to send a SETTINGS ack.
157   EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsSettings(testing::IsEmpty())));
158   EXPECT_CALL(mock_callbacks_, OnFrameSend(IsSettings(testing::IsEmpty())));
159   EXPECT_TRUE(nghttp2_session_want_write(session_.get()));
160   result = nghttp2_session_send(session_.get());
161   EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
162   serialized_.clear();
163 
164   EXPECT_FALSE(nghttp2_session_want_write(session_.get()));
165 
166   // The following sets up the client request.
167   std::vector<std::pair<absl::string_view, absl::string_view>> headers = {
168       {":method", "POST"},
169       {":scheme", "http"},
170       {":authority", "example.com"},
171       {":path", "/this/is/request/one"}};
172   std::vector<nghttp2_nv> nvs;
173   for (const auto& h : headers) {
174     nvs.push_back({.name = ToUint8Ptr(h.first.data()),
175                    .value = ToUint8Ptr(h.second.data()),
176                    .namelen = h.first.size(),
177                    .valuelen = h.second.size(),
178                    .flags = NGHTTP2_NV_FLAG_NONE});
179   }
180   const absl::string_view kBody = "This is an example request body.";
181   TestDataSource source{kBody};
182   nghttp2_data_provider provider = source.MakeDataProvider();
183   // After submitting the request, the client will want to write.
184   int stream_id =
185       nghttp2_submit_request(session_.get(), nullptr /* pri_spec */, nvs.data(),
186                              nvs.size(), &provider, nullptr /* stream_data */);
187   EXPECT_GT(stream_id, 0);
188   EXPECT_TRUE(nghttp2_session_want_write(session_.get()));
189 
190   // We expect that the client will want to write HEADERS, then DATA.
191   EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsHeaders(stream_id, _, _)));
192   EXPECT_CALL(mock_callbacks_, OnFrameSend(IsHeaders(stream_id, _, _)));
193   EXPECT_CALL(mock_callbacks_, OnFrameSend(IsData(stream_id, kBody.size(), _)));
194   nghttp2_session_send(session_.get());
195   EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::HEADERS,
196                                          spdy::SpdyFrameType::DATA}));
197   EXPECT_THAT(serialized_, testing::HasSubstr(kBody));
198 
199   // Once the request is flushed, the client no longer wants to write.
200   EXPECT_FALSE(nghttp2_session_want_write(session_.get()));
201 }
202 
203 class Nghttp2ServerTest : public Nghttp2Test {
204  public:
GetPerspective()205   Perspective GetPerspective() override { return Perspective::kServer; }
206 };
207 
208 // Verifies the behavior when a stream ends early.
TEST_F(Nghttp2ServerTest,MismatchedContentLength)209 TEST_F(Nghttp2ServerTest, MismatchedContentLength) {
210   const std::string initial_frames =
211       TestFrameSequence()
212           .ClientPreface()
213           .Headers(1,
214                    {{":method", "POST"},
215                     {":scheme", "https"},
216                     {":authority", "example.com"},
217                     {":path", "/"},
218                     {"content-length", "50"}},
219                    /*fin=*/false)
220           .Data(1, "Less than 50 bytes.", true)
221           .Serialize();
222 
223   testing::InSequence seq;
224   EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, _)));
225 
226   EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));
227 
228   // HEADERS on stream 1
229   EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(
230                                    1, HEADERS, NGHTTP2_FLAG_END_HEADERS)));
231 
232   EXPECT_CALL(mock_callbacks_,
233               OnBeginHeaders(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS,
234                                        NGHTTP2_HCAT_REQUEST)));
235 
236   EXPECT_CALL(mock_callbacks_, OnHeader(_, ":method", "POST", _));
237   EXPECT_CALL(mock_callbacks_, OnHeader(_, ":scheme", "https", _));
238   EXPECT_CALL(mock_callbacks_, OnHeader(_, ":authority", "example.com", _));
239   EXPECT_CALL(mock_callbacks_, OnHeader(_, ":path", "/", _));
240   EXPECT_CALL(mock_callbacks_, OnHeader(_, "content-length", "50", _));
241   EXPECT_CALL(mock_callbacks_,
242               OnFrameRecv(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS,
243                                     NGHTTP2_HCAT_REQUEST)));
244 
245   // DATA on stream 1
246   EXPECT_CALL(mock_callbacks_,
247               OnBeginFrame(HasFrameHeader(1, DATA, NGHTTP2_FLAG_END_STREAM)));
248 
249   EXPECT_CALL(mock_callbacks_, OnDataChunkRecv(NGHTTP2_FLAG_END_STREAM, 1,
250                                                "Less than 50 bytes."));
251 
252   // No OnFrameRecv() callback for the DATA frame, since there is a
253   // Content-Length mismatch error.
254 
255   ssize_t result = nghttp2_session_mem_recv(
256       session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
257   ASSERT_EQ(result, initial_frames.size());
258 }
259 
260 }  // namespace
261 }  // namespace test
262 }  // namespace adapter
263 }  // namespace http2
264