Adds simple decoder event handling to OgHttp2Session.
The session mostly passes events through to the visitor, although this change does contain some minimal state tracking.
PiperOrigin-RevId: 368932801
Change-Id: I006a9c3d2e156b64f294866de6fc3e92fab9dfe2
diff --git a/http2/adapter/http2_util.cc b/http2/adapter/http2_util.cc
index 40deeb2..e054a82 100644
--- a/http2/adapter/http2_util.cc
+++ b/http2/adapter/http2_util.cc
@@ -36,5 +36,38 @@
}
}
+Http2ErrorCode TranslateErrorCode(spdy::SpdyErrorCode code) {
+ switch (code) {
+ case spdy::ERROR_CODE_NO_ERROR:
+ return Http2ErrorCode::NO_ERROR;
+ case spdy::ERROR_CODE_PROTOCOL_ERROR:
+ return Http2ErrorCode::PROTOCOL_ERROR;
+ case spdy::ERROR_CODE_INTERNAL_ERROR:
+ return Http2ErrorCode::INTERNAL_ERROR;
+ case spdy::ERROR_CODE_FLOW_CONTROL_ERROR:
+ return Http2ErrorCode::FLOW_CONTROL_ERROR;
+ case spdy::ERROR_CODE_SETTINGS_TIMEOUT:
+ return Http2ErrorCode::SETTINGS_TIMEOUT;
+ case spdy::ERROR_CODE_STREAM_CLOSED:
+ return Http2ErrorCode::STREAM_CLOSED;
+ case spdy::ERROR_CODE_FRAME_SIZE_ERROR:
+ return Http2ErrorCode::FRAME_SIZE_ERROR;
+ case spdy::ERROR_CODE_REFUSED_STREAM:
+ return Http2ErrorCode::REFUSED_STREAM;
+ case spdy::ERROR_CODE_CANCEL:
+ return Http2ErrorCode::CANCEL;
+ case spdy::ERROR_CODE_COMPRESSION_ERROR:
+ return Http2ErrorCode::COMPRESSION_ERROR;
+ case spdy::ERROR_CODE_CONNECT_ERROR:
+ return Http2ErrorCode::CONNECT_ERROR;
+ case spdy::ERROR_CODE_ENHANCE_YOUR_CALM:
+ return Http2ErrorCode::ENHANCE_YOUR_CALM;
+ case spdy::ERROR_CODE_INADEQUATE_SECURITY:
+ return Http2ErrorCode::INADEQUATE_SECURITY;
+ case spdy::ERROR_CODE_HTTP_1_1_REQUIRED:
+ return Http2ErrorCode::HTTP_1_1_REQUIRED;
+ }
+}
+
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/http2_util.h b/http2/adapter/http2_util.h
index e9ae2a5..3ace28b 100644
--- a/http2/adapter/http2_util.h
+++ b/http2/adapter/http2_util.h
@@ -8,6 +8,7 @@
namespace adapter {
spdy::SpdyErrorCode TranslateErrorCode(Http2ErrorCode code);
+Http2ErrorCode TranslateErrorCode(spdy::SpdyErrorCode code);
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index fef15aa..799c9bf 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -1,6 +1,7 @@
#include "http2/adapter/oghttp2_adapter.h"
#include "http2/adapter/mock_http2_visitor.h"
+#include "http2/adapter/test_frame_sequence.h"
#include "http2/adapter/test_utils.h"
#include "common/platform/api/quiche_test.h"
#include "common/platform/api/quiche_test_helpers.h"
@@ -13,7 +14,7 @@
class OgHttp2AdapterTest : public testing::Test {
protected:
void SetUp() override {
- OgHttp2Adapter::Options options;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
adapter_ = OgHttp2Adapter::Create(http2_visitor_, options);
}
@@ -22,7 +23,11 @@
};
TEST_F(OgHttp2AdapterTest, ProcessBytes) {
- EXPECT_QUICHE_BUG(adapter_->ProcessBytes("fake data"), "Not implemented");
+ EXPECT_CALL(http2_visitor_, OnSettingsStart());
+ EXPECT_CALL(http2_visitor_, OnSettingsEnd());
+ EXPECT_CALL(http2_visitor_, OnPing(17, false));
+ adapter_->ProcessBytes(
+ TestFrameSequence().ClientPreface().Ping(17).Serialize());
}
TEST_F(OgHttp2AdapterTest, SubmitMetadata) {
@@ -30,9 +35,7 @@
}
TEST_F(OgHttp2AdapterTest, GetPeerConnectionWindow) {
- int peer_window = 0;
- EXPECT_QUICHE_BUG(peer_window = adapter_->GetPeerConnectionWindow(),
- "Not implemented");
+ const int peer_window = adapter_->GetPeerConnectionWindow();
EXPECT_GT(peer_window, 0);
}
@@ -42,7 +45,7 @@
}
TEST_F(OgHttp2AdapterTest, TestSerialize) {
- EXPECT_FALSE(adapter_->session().want_read());
+ EXPECT_TRUE(adapter_->session().want_read());
EXPECT_FALSE(adapter_->session().want_write());
adapter_->SubmitSettings(
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index bcb47d6..f434c3c 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -3,9 +3,58 @@
namespace http2 {
namespace adapter {
+void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockStart() {
+ visitor_.OnBeginHeadersForStream(stream_id_);
+}
+
+void OgHttp2Session::PassthroughHeadersHandler::OnHeader(
+ absl::string_view key,
+ absl::string_view value) {
+ visitor_.OnHeaderForStream(stream_id_, key, value);
+}
+
+void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockEnd(
+ size_t /* uncompressed_header_bytes */,
+ size_t /* compressed_header_bytes */) {
+ visitor_.OnEndHeadersForStream(stream_id_);
+}
+
+OgHttp2Session::OgHttp2Session(Http2VisitorInterface& visitor, Options options)
+ : visitor_(visitor), headers_handler_(visitor), options_(options) {
+ decoder_.set_visitor(this);
+ if (options_.perspective == Perspective::kServer) {
+ remaining_preface_ = {spdy::kHttp2ConnectionHeaderPrefix,
+ spdy::kHttp2ConnectionHeaderPrefixSize};
+ }
+}
+
+OgHttp2Session::~OgHttp2Session() {}
+
ssize_t OgHttp2Session::ProcessBytes(absl::string_view bytes) {
- QUICHE_BUG(oghttp2_process_bytes) << "Not implemented";
- return 0;
+ ssize_t preface_consumed = 0;
+ if (!remaining_preface_.empty()) {
+ QUICHE_VLOG(2) << "Preface bytes remaining: " << remaining_preface_.size();
+ // decoder_ does not understand the client connection preface.
+ size_t min_size = std::min(remaining_preface_.size(), bytes.size());
+ if (!absl::StartsWith(remaining_preface_, bytes.substr(0, min_size))) {
+ // Preface doesn't match!
+ QUICHE_DLOG(INFO) << "Preface doesn't match! Expected: ["
+ << absl::CEscape(remaining_preface_) << "], actual: ["
+ << absl::CEscape(bytes) << "]";
+ visitor_.OnConnectionError();
+ return -1;
+ }
+ remaining_preface_.remove_prefix(min_size);
+ bytes.remove_prefix(min_size);
+ if (!remaining_preface_.empty()) {
+ QUICHE_VLOG(2) << "Preface bytes remaining: "
+ << remaining_preface_.size();
+ return min_size;
+ }
+ preface_consumed = min_size;
+ }
+ ssize_t result = decoder_.ProcessInput(bytes.data(), bytes.size());
+ return result < 0 ? result : result + preface_consumed;
}
int OgHttp2Session::Consume(Http2StreamId stream_id, size_t num_bytes) {
@@ -40,5 +89,136 @@
return serialized;
}
+void OgHttp2Session::OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+ std::string detailed_error) {
+ QUICHE_VLOG(1) << "Error: "
+ << http2::Http2DecoderAdapter::SpdyFramerErrorToString(error)
+ << " details: " << detailed_error;
+ visitor_.OnConnectionError();
+}
+
+void OgHttp2Session::OnCommonHeader(spdy::SpdyStreamId /*stream_id*/,
+ size_t /*length*/,
+ uint8_t /*type*/,
+ uint8_t /*flags*/) {}
+
+void OgHttp2Session::OnDataFrameHeader(spdy::SpdyStreamId stream_id,
+ size_t length,
+ bool fin) {
+ visitor_.OnBeginDataForStream(stream_id, length);
+}
+
+void OgHttp2Session::OnStreamFrameData(spdy::SpdyStreamId stream_id,
+ const char* data,
+ size_t len) {
+ visitor_.OnDataForStream(stream_id, absl::string_view(data, len));
+}
+
+void OgHttp2Session::OnStreamEnd(spdy::SpdyStreamId stream_id) {
+ visitor_.OnEndStream(stream_id);
+}
+
+void OgHttp2Session::OnStreamPadLength(spdy::SpdyStreamId /*stream_id*/,
+ size_t /*value*/) {}
+
+void OgHttp2Session::OnStreamPadding(spdy::SpdyStreamId stream_id, size_t len) {
+}
+
+spdy::SpdyHeadersHandlerInterface* OgHttp2Session::OnHeaderFrameStart(
+ spdy::SpdyStreamId stream_id) {
+ headers_handler_.set_stream_id(stream_id);
+ return &headers_handler_;
+}
+
+void OgHttp2Session::OnHeaderFrameEnd(spdy::SpdyStreamId stream_id) {
+ headers_handler_.set_stream_id(0);
+}
+
+void OgHttp2Session::OnRstStream(spdy::SpdyStreamId stream_id,
+ spdy::SpdyErrorCode error_code) {
+ visitor_.OnRstStream(stream_id, TranslateErrorCode(error_code));
+ visitor_.OnAbortStream(stream_id, TranslateErrorCode(error_code));
+}
+
+void OgHttp2Session::OnSettings() {
+ visitor_.OnSettingsStart();
+}
+
+void OgHttp2Session::OnSetting(spdy::SpdySettingsId id, uint32_t value) {
+ visitor_.OnSetting({id, value});
+}
+
+void OgHttp2Session::OnSettingsEnd() {
+ visitor_.OnSettingsEnd();
+}
+
+void OgHttp2Session::OnSettingsAck() {
+ visitor_.OnSettingsAck();
+}
+
+void OgHttp2Session::OnPing(spdy::SpdyPingId unique_id, bool is_ack) {
+ visitor_.OnPing(unique_id, is_ack);
+}
+
+void OgHttp2Session::OnGoAway(spdy::SpdyStreamId last_accepted_stream_id,
+ spdy::SpdyErrorCode error_code) {
+ received_goaway_ = true;
+ visitor_.OnGoAway(last_accepted_stream_id, TranslateErrorCode(error_code),
+ "");
+}
+
+bool OgHttp2Session::OnGoAwayFrameData(const char* goaway_data, size_t len) {
+ // Opaque data is currently ignored.
+ return true;
+}
+
+void OgHttp2Session::OnHeaders(spdy::SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ spdy::SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end) {}
+
+void OgHttp2Session::OnWindowUpdate(spdy::SpdyStreamId stream_id,
+ int delta_window_size) {
+ if (stream_id == 0) {
+ peer_window_ += delta_window_size;
+ } else {
+ auto it = stream_map_.find(stream_id);
+ if (it == stream_map_.end()) {
+ QUICHE_VLOG(1) << "Stream " << stream_id << " not found!";
+ } else {
+ it->second.send_window += delta_window_size;
+ }
+ }
+ visitor_.OnWindowUpdate(stream_id, delta_window_size);
+}
+
+void OgHttp2Session::OnPushPromise(spdy::SpdyStreamId stream_id,
+ spdy::SpdyStreamId promised_stream_id,
+ bool end) {}
+
+void OgHttp2Session::OnContinuation(spdy::SpdyStreamId stream_id, bool end) {}
+
+void OgHttp2Session::OnAltSvc(spdy::SpdyStreamId /*stream_id*/,
+ absl::string_view /*origin*/,
+ const spdy::SpdyAltSvcWireFormat::
+ AlternativeServiceVector& /*altsvc_vector*/) {
+}
+
+void OgHttp2Session::OnPriority(spdy::SpdyStreamId stream_id,
+ spdy::SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive) {}
+
+void OgHttp2Session::OnPriorityUpdate(spdy::SpdyStreamId prioritized_stream_id,
+ absl::string_view priority_field_value) {}
+
+bool OgHttp2Session::OnUnknownFrame(spdy::SpdyStreamId stream_id,
+ uint8_t frame_type) {
+ return true;
+}
+
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index dfd0220..b0bc28b 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -4,23 +4,26 @@
#include <list>
#include "http2/adapter/http2_session.h"
+#include "http2/adapter/http2_util.h"
#include "http2/adapter/http2_visitor_interface.h"
#include "http2/adapter/window_manager.h"
#include "common/platform/api/quiche_bug_tracker.h"
+#include "spdy/core/http2_frame_decoder_adapter.h"
#include "spdy/core/spdy_framer.h"
namespace http2 {
namespace adapter {
// This class manages state associated with a single multiplexed HTTP/2 session.
-class OgHttp2Session : public Http2Session {
+class OgHttp2Session : public Http2Session,
+ public spdy::SpdyFramerVisitorInterface {
public:
struct Options {
- Perspective context;
+ Perspective perspective = Perspective::kClient;
};
- OgHttp2Session(Http2VisitorInterface& /*visitor*/, Options /*options*/) {}
- ~OgHttp2Session() override {}
+ OgHttp2Session(Http2VisitorInterface& visitor, Options /*options*/);
+ ~OgHttp2Session() override;
// Enqueues a frame for transmission to the peer.
void EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame);
@@ -32,27 +35,104 @@
// From Http2Session.
ssize_t ProcessBytes(absl::string_view bytes) override;
int Consume(Http2StreamId stream_id, size_t num_bytes) override;
- bool want_read() const override { return false; }
+ bool want_read() const override { return !received_goaway_; }
bool want_write() const override {
return !frames_.empty() || !serialized_prefix_.empty();
}
int GetRemoteWindowSize() const override {
- QUICHE_BUG(peer_window_not_updated) << "Not implemented";
return peer_window_;
}
+ // From SpdyFramerVisitorInterface
+ void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+ std::string detailed_error) override;
+ void OnCommonHeader(spdy::SpdyStreamId /*stream_id*/,
+ size_t /*length*/,
+ uint8_t /*type*/,
+ uint8_t /*flags*/) override;
+ void OnDataFrameHeader(spdy::SpdyStreamId stream_id,
+ size_t length,
+ bool fin) override;
+ void OnStreamFrameData(spdy::SpdyStreamId stream_id,
+ const char* data,
+ size_t len) override;
+ void OnStreamEnd(spdy::SpdyStreamId stream_id) override;
+ void OnStreamPadLength(spdy::SpdyStreamId /*stream_id*/,
+ size_t /*value*/) override;
+ void OnStreamPadding(spdy::SpdyStreamId stream_id, size_t len) override;
+ spdy::SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+ spdy::SpdyStreamId stream_id) override;
+ void OnHeaderFrameEnd(spdy::SpdyStreamId stream_id) override;
+ void OnRstStream(spdy::SpdyStreamId stream_id,
+ spdy::SpdyErrorCode error_code) override;
+ void OnSettings() override;
+ void OnSetting(spdy::SpdySettingsId id, uint32_t value) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck() override;
+ void OnPing(spdy::SpdyPingId unique_id, bool is_ack) override;
+ void OnGoAway(spdy::SpdyStreamId last_accepted_stream_id,
+ spdy::SpdyErrorCode error_code) override;
+ bool OnGoAwayFrameData(const char* goaway_data, size_t len);
+ void OnHeaders(spdy::SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ spdy::SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end) override;
+ void OnWindowUpdate(spdy::SpdyStreamId stream_id,
+ int delta_window_size) override;
+ void OnPushPromise(spdy::SpdyStreamId stream_id,
+ spdy::SpdyStreamId promised_stream_id,
+ bool end) override;
+ void OnContinuation(spdy::SpdyStreamId stream_id, bool end) override;
+ void OnAltSvc(spdy::SpdyStreamId /*stream_id*/,
+ absl::string_view /*origin*/,
+ const spdy::SpdyAltSvcWireFormat::
+ AlternativeServiceVector& /*altsvc_vector*/);
+ void OnPriority(spdy::SpdyStreamId stream_id,
+ spdy::SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive) override;
+ void OnPriorityUpdate(spdy::SpdyStreamId prioritized_stream_id,
+ absl::string_view priority_field_value) override;
+ bool OnUnknownFrame(spdy::SpdyStreamId stream_id,
+ uint8_t frame_type) override;
+
private:
struct StreamState {
WindowManager window_manager;
+ int32_t send_window = 65535;
bool half_closed_local = false;
bool half_closed_remote = false;
};
+ class PassthroughHeadersHandler : public spdy::SpdyHeadersHandlerInterface {
+ public:
+ explicit PassthroughHeadersHandler(Http2VisitorInterface& visitor)
+ : visitor_(visitor) {}
+ void set_stream_id(Http2StreamId stream_id) { stream_id_ = stream_id; }
+ void OnHeaderBlockStart() override;
+ void OnHeader(absl::string_view key, absl::string_view value) override;
+ void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */,
+ size_t /* compressed_header_bytes */) override;
+
+ private:
+ Http2VisitorInterface& visitor_;
+ Http2StreamId stream_id_ = 0;
+ };
+
+ Http2VisitorInterface& visitor_;
spdy::SpdyFramer framer_{spdy::SpdyFramer::ENABLE_COMPRESSION};
+ http2::Http2DecoderAdapter decoder_;
absl::flat_hash_map<Http2StreamId, StreamState> stream_map_;
std::list<std::unique_ptr<spdy::SpdyFrameIR>> frames_;
+ PassthroughHeadersHandler headers_handler_;
std::string serialized_prefix_;
+ absl::string_view remaining_preface_;
int peer_window_ = 65535;
+ Options options_;
+ bool received_goaway_ = false;
};
} // namespace adapter
diff --git a/http2/adapter/oghttp2_session_test.cc b/http2/adapter/oghttp2_session_test.cc
new file mode 100644
index 0000000..7de1514
--- /dev/null
+++ b/http2/adapter/oghttp2_session_test.cc
@@ -0,0 +1,146 @@
+#include "http2/adapter/oghttp2_session.h"
+
+#include "http2/adapter/mock_http2_visitor.h"
+#include "http2/adapter/test_frame_sequence.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+
+TEST(OgHttp2SessionTest, ClientConstruction) {
+ testing::StrictMock<MockHttp2Visitor> visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_TRUE(session.want_read());
+ EXPECT_FALSE(session.want_write());
+ EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize);
+}
+
+TEST(OgHttp2SessionTest, ClientHandlesFrames) {
+ testing::StrictMock<MockHttp2Visitor> visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+
+ const std::string initial_frames = TestFrameSequence()
+ .ServerPreface()
+ .Ping(42)
+ .WindowUpdate(0, 1000)
+ .Serialize();
+ testing::InSequence s;
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ EXPECT_CALL(visitor, OnPing(42, false));
+ EXPECT_CALL(visitor, OnWindowUpdate(0, 1000));
+
+ const ssize_t initial_result = session.ProcessBytes(initial_frames);
+ EXPECT_EQ(initial_frames.size(), initial_result);
+
+ EXPECT_EQ(session.GetRemoteWindowSize(),
+ kDefaultInitialStreamWindowSize + 1000);
+
+ // Should OgHttp2Session require that streams 1 and 3 have been created?
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .Headers(1,
+ {{":status", "200"},
+ {"server", "my-fake-server"},
+ {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}},
+ /*fin=*/false)
+ .Data(1, "This is the response body.")
+ .RstStream(3, Http2ErrorCode::INTERNAL_ERROR)
+ .GoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!")
+ .Serialize();
+
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server"));
+ EXPECT_CALL(visitor,
+ OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, 26));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body."));
+ EXPECT_CALL(visitor, OnRstStream(3, Http2ErrorCode::INTERNAL_ERROR));
+ EXPECT_CALL(visitor, OnAbortStream(3, Http2ErrorCode::INTERNAL_ERROR));
+ EXPECT_CALL(visitor, OnGoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, ""));
+ const ssize_t stream_result = session.ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+}
+
+TEST(OgHttp2SessionTest, ServerConstruction) {
+ testing::StrictMock<MockHttp2Visitor> visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+ EXPECT_TRUE(session.want_read());
+ EXPECT_FALSE(session.want_write());
+ EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize);
+}
+
+TEST(OgHttp2SessionTest, ServerHandlesFrames) {
+ testing::StrictMock<MockHttp2Visitor> visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Ping(42)
+ .WindowUpdate(0, 1000)
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/false)
+ .WindowUpdate(1, 2000)
+ .Data(1, "This is the request body.")
+ .Headers(3,
+ {{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/two"}},
+ /*fin=*/true)
+ .RstStream(3, Http2ErrorCode::CANCEL)
+ .Ping(47)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ EXPECT_CALL(visitor, OnPing(42, false));
+ EXPECT_CALL(visitor, OnWindowUpdate(0, 1000));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnWindowUpdate(1, 2000));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, 25));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body."));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(3));
+ EXPECT_CALL(visitor, OnHeaderForStream(3, ":method", "GET"));
+ EXPECT_CALL(visitor, OnHeaderForStream(3, ":scheme", "http"));
+ EXPECT_CALL(visitor, OnHeaderForStream(3, ":authority", "example.com"));
+ EXPECT_CALL(visitor, OnHeaderForStream(3, ":path", "/this/is/request/two"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(3));
+ EXPECT_CALL(visitor, OnEndStream(3));
+ EXPECT_CALL(visitor, OnRstStream(3, Http2ErrorCode::CANCEL));
+ EXPECT_CALL(visitor, OnAbortStream(3, Http2ErrorCode::CANCEL));
+ EXPECT_CALL(visitor, OnPing(47, false));
+
+ const ssize_t result = session.ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ EXPECT_EQ(session.GetRemoteWindowSize(),
+ kDefaultInitialStreamWindowSize + 1000);
+}
+
+} // namespace test
+} // namespace adapter
+} // namespace http2