Handle outbound SETTINGS_MAX_FRAME_SIZE in oghttp2.
PiperOrigin-RevId: 434784601
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index 0294344..fe4e32c 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -2756,6 +2756,143 @@
EXPECT_EQ(result, NGHTTP2_ERR_CALLBACK_FAILURE);
}
+TEST(NgHttp2AdapterTest, MaxFrameSizeSettingNotAppliedBeforeAck) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ const uint32_t large_frame_size = kDefaultFramePayloadSizeLimit + 42;
+ adapter->SubmitSettings({{MAX_FRAME_SIZE, large_frame_size}});
+ const int32_t stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ /*data_source=*/nullptr, /*user_data=*/nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->want_write());
+
+ testing::InSequence s;
+
+ // Client preface (SETTINGS with MAX_FRAME_SIZE) and request HEADERS
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string server_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .Headers(1, {{":status", "200"}}, /*fin=*/false)
+ .Data(1, std::string(large_frame_size, 'a'))
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ // Response HEADERS. Because the SETTINGS with MAX_FRAME_SIZE was not
+ // acknowledged, the large DATA is treated as a connection error. Note that
+ // nghttp2 does not deliver any DATA or connection error events.
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+
+ const int64_t process_result = adapter->ProcessBytes(server_frames);
+ EXPECT_EQ(server_frames.size(), static_cast<size_t>(process_result));
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::FRAME_SIZE_ERROR)));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY}));
+}
+
+TEST(NgHttp2AdapterTest, MaxFrameSizeSettingAppliedAfterAck) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ const uint32_t large_frame_size = kDefaultFramePayloadSizeLimit + 42;
+ adapter->SubmitSettings({{MAX_FRAME_SIZE, large_frame_size}});
+ const int32_t stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ /*data_source=*/nullptr, /*user_data=*/nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->want_write());
+
+ testing::InSequence s;
+
+ // Client preface (SETTINGS with MAX_FRAME_SIZE) and request HEADERS
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string server_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .SettingsAck()
+ .Headers(1, {{":status", "200"}}, /*fin=*/false)
+ .Data(1, std::string(large_frame_size, 'a'))
+ .Serialize();
+
+ // Server preface (empty SETTINGS) and ack of SETTINGS.
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0x1));
+ EXPECT_CALL(visitor, OnSettingsAck());
+
+ // Response HEADERS and DATA. Because the SETTINGS with MAX_FRAME_SIZE was
+ // acknowledged, the large DATA is accepted without any error.
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnFrameHeader(1, large_frame_size, DATA, 0x0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, large_frame_size));
+ EXPECT_CALL(visitor, OnDataForStream(1, _));
+
+ const int64_t process_result = adapter->ProcessBytes(server_frames);
+ EXPECT_EQ(server_frames.size(), static_cast<size_t>(process_result));
+
+ // Client ack of SETTINGS.
+ EXPECT_TRUE(adapter->want_write());
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
+}
+
TEST(NgHttp2AdapterTest, ConnectionErrorOnControlFrameSent) {
DataSavingVisitor visitor;
auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index b7fa20c..1925173 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -2472,6 +2472,147 @@
EXPECT_LT(result, 0);
}
+TEST(OgHttp2AdapterTest, MaxFrameSizeSettingNotAppliedBeforeAck) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ const uint32_t large_frame_size = kDefaultFramePayloadSizeLimit + 42;
+ adapter->SubmitSettings({{MAX_FRAME_SIZE, large_frame_size}});
+ const int32_t stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ /*data_source=*/nullptr, /*user_data=*/nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->want_write());
+
+ testing::InSequence s;
+
+ // Client preface (SETTINGS with MAX_FRAME_SIZE) and request HEADERS
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string server_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .Headers(1, {{":status", "200"}}, /*fin=*/false)
+ .Data(1, std::string(large_frame_size, 'a'))
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ // Response HEADERS. Because the SETTINGS with MAX_FRAME_SIZE was not
+ // acknowledged, the large DATA is treated as a connection error. Note that
+ // oghttp2 delivers the DATA frame header and connection error events.
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnFrameHeader(1, large_frame_size, DATA, 0x0));
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
+
+ const int64_t process_result = adapter->ProcessBytes(server_frames);
+ EXPECT_EQ(server_frames.size(), static_cast<size_t>(process_result));
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::FRAME_SIZE_ERROR)));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY}));
+}
+
+TEST(OgHttp2AdapterTest, MaxFrameSizeSettingAppliedAfterAck) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ const uint32_t large_frame_size = kDefaultFramePayloadSizeLimit + 42;
+ adapter->SubmitSettings({{MAX_FRAME_SIZE, large_frame_size}});
+ const int32_t stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ /*data_source=*/nullptr, /*user_data=*/nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->want_write());
+
+ testing::InSequence s;
+
+ // Client preface (SETTINGS with MAX_FRAME_SIZE) and request HEADERS
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string server_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .SettingsAck()
+ .Headers(1, {{":status", "200"}}, /*fin=*/false)
+ .Data(1, std::string(large_frame_size, 'a'))
+ .Serialize();
+
+ // Server preface (empty SETTINGS) and ack of SETTINGS.
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0x1));
+ EXPECT_CALL(visitor, OnSettingsAck());
+
+ // Response HEADERS and DATA. Because the SETTINGS with MAX_FRAME_SIZE was
+ // acknowledged, the large DATA is accepted without any error.
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnFrameHeader(1, large_frame_size, DATA, 0x0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, large_frame_size));
+ EXPECT_CALL(visitor, OnDataForStream(1, _));
+
+ const int64_t process_result = adapter->ProcessBytes(server_frames);
+ EXPECT_EQ(server_frames.size(), static_cast<size_t>(process_result));
+
+ // Client ack of SETTINGS.
+ EXPECT_TRUE(adapter->want_write());
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
+}
+
TEST(OgHttp2AdapterTest, ClientForbidsPushPromise) {
DataSavingVisitor visitor;
OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
@@ -4263,8 +4404,6 @@
// Now let the client ack the MAX_FRAME_SIZE SETTINGS and send a DATA frame to
// overflow the connection-level window. The result should be a GOAWAY.
- // TODO(b/223471995): The result is a GOAWAY, but not for the right reason.
- // Fix oghttp2 to handle outbound SETTINGS with MAX_FRAME_SIZE.
const std::string overflow_frames =
TestFrameSequence()
.SettingsAck()
@@ -4274,16 +4413,17 @@
EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0x1));
EXPECT_CALL(visitor, OnSettingsAck());
EXPECT_CALL(visitor, OnFrameHeader(1, window_overflow_bytes, DATA, 0x0));
- EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kFlowControlError));
// No further frame data is delivered.
process_result = adapter->ProcessBytes(overflow_frames);
EXPECT_EQ(overflow_frames.size(), static_cast<size_t>(process_result));
EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
- EXPECT_CALL(visitor,
- OnFrameSent(GOAWAY, 0, _, 0x0,
- static_cast<int>(Http2ErrorCode::FRAME_SIZE_ERROR)));
+ EXPECT_CALL(
+ visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::FLOW_CONTROL_ERROR)));
send_result = adapter->Send();
EXPECT_EQ(0, send_result);
@@ -4344,8 +4484,6 @@
// Now let the client ack the MAX_FRAME_SIZE SETTINGS and send a DATA frame to
// overflow the connection-level window. The result should be a GOAWAY.
- // TODO(b/223471995): The result is a GOAWAY, but not for the right reason.
- // Fix oghttp2 to handle outbound SETTINGS with MAX_FRAME_SIZE.
const std::string overflow_frames =
TestFrameSequence()
.SettingsAck()
@@ -4355,7 +4493,7 @@
EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0x1));
EXPECT_CALL(visitor, OnSettingsAck());
EXPECT_CALL(visitor, OnFrameHeader(1, window_overflow_bytes, DATA, 0x0));
- EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kFlowControlError));
const size_t chunk_length = 16384;
ASSERT_GE(overflow_frames.size(), chunk_length);
@@ -4364,9 +4502,10 @@
EXPECT_EQ(chunk_length, static_cast<size_t>(process_result));
EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
- EXPECT_CALL(visitor,
- OnFrameSent(GOAWAY, 0, _, 0x0,
- static_cast<int>(Http2ErrorCode::FRAME_SIZE_ERROR)));
+ EXPECT_CALL(
+ visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::FLOW_CONTROL_ERROR)));
send_result = adapter->Send();
EXPECT_EQ(0, send_result);
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 9a120c3..25cd0e9 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -1591,8 +1591,10 @@
UpdateStreamReceiveWindowSizes(value);
initial_stream_receive_window_ = value;
break;
- case ENABLE_PUSH:
case MAX_FRAME_SIZE:
+ decoder_.SetMaxFrameSize(value);
+ break;
+ case ENABLE_PUSH:
case MAX_HEADER_LIST_SIZE:
case ENABLE_CONNECT_PROTOCOL:
QUICHE_VLOG(2)