Adds the initial version of NgHttp2Adapter. This mostly passes calls on the adapter through to the nghttp2 library with some simple type conversion. Based on prior art by diannahu@. PiperOrigin-RevId: 372668442
diff --git a/http2/adapter/nghttp2_adapter.cc b/http2/adapter/nghttp2_adapter.cc new file mode 100644 index 0000000..878c040 --- /dev/null +++ b/http2/adapter/nghttp2_adapter.cc
@@ -0,0 +1,149 @@ +#include "http2/adapter/nghttp2_adapter.h" + +#include "absl/algorithm/container.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "http2/adapter/nghttp2_callbacks.h" +#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" +#include "common/platform/api/quiche_logging.h" +#include "common/quiche_endian.h" + +namespace http2 { +namespace adapter { + +/* static */ +std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateClientAdapter( + Http2VisitorInterface& visitor) { + auto adapter = new NgHttp2Adapter(visitor, Perspective::kClient); + adapter->Initialize(); + return absl::WrapUnique(adapter); +} + +/* static */ +std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateServerAdapter( + Http2VisitorInterface& visitor) { + auto adapter = new NgHttp2Adapter(visitor, Perspective::kServer); + adapter->Initialize(); + return absl::WrapUnique(adapter); +} + +ssize_t NgHttp2Adapter::ProcessBytes(absl::string_view bytes) { + const ssize_t processed_bytes = session_->ProcessBytes(bytes); + if (processed_bytes < 0) { + visitor_.OnConnectionError(); + } + return processed_bytes; +} + +void NgHttp2Adapter::SubmitSettings(absl::Span<const Http2Setting> settings) { + // Submit SETTINGS, converting each Http2Setting to an nghttp2_settings_entry. + std::vector<nghttp2_settings_entry> nghttp2_settings; + absl::c_transform(settings, std::back_inserter(nghttp2_settings), + [](const Http2Setting& setting) { + return nghttp2_settings_entry{setting.id, setting.value}; + }); + nghttp2_submit_settings(session_->raw_ptr(), NGHTTP2_FLAG_NONE, + nghttp2_settings.data(), nghttp2_settings.size()); +} + +void NgHttp2Adapter::SubmitPriorityForStream(Http2StreamId stream_id, + Http2StreamId parent_stream_id, + int weight, + bool exclusive) { + nghttp2_priority_spec priority_spec; + nghttp2_priority_spec_init(&priority_spec, parent_stream_id, weight, + static_cast<int>(exclusive)); + nghttp2_submit_priority(session_->raw_ptr(), NGHTTP2_FLAG_NONE, stream_id, + &priority_spec); +} + +void NgHttp2Adapter::SubmitPing(Http2PingId ping_id) { + uint8_t opaque_data[8] = {}; + Http2PingId ping_id_to_serialize = quiche::QuicheEndian::HostToNet64(ping_id); + std::memcpy(opaque_data, &ping_id_to_serialize, sizeof(Http2PingId)); + nghttp2_submit_ping(session_->raw_ptr(), NGHTTP2_FLAG_NONE, opaque_data); +} + +void NgHttp2Adapter::SubmitGoAway(Http2StreamId last_accepted_stream_id, + Http2ErrorCode error_code, + absl::string_view opaque_data) { + nghttp2_submit_goaway(session_->raw_ptr(), NGHTTP2_FLAG_NONE, + last_accepted_stream_id, + static_cast<uint32_t>(error_code), + ToUint8Ptr(opaque_data.data()), opaque_data.size()); +} + +void NgHttp2Adapter::SubmitWindowUpdate(Http2StreamId stream_id, + int window_increment) { + nghttp2_submit_window_update(session_->raw_ptr(), NGHTTP2_FLAG_NONE, + stream_id, window_increment); +} + +void NgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id, + bool end_metadata) { + QUICHE_LOG(DFATAL) << "Not implemented"; +} + +std::string NgHttp2Adapter::GetBytesToWrite(absl::optional<size_t> max_bytes) { + ssize_t num_bytes = 0; + std::string result; + do { + const uint8_t* data = nullptr; + num_bytes = nghttp2_session_mem_send(session_->raw_ptr(), &data); + if (num_bytes > 0) { + absl::StrAppend( + &result, + absl::string_view(reinterpret_cast<const char*>(data), num_bytes)); + } else if (num_bytes < 0) { + visitor_.OnConnectionError(); + } + } while (num_bytes > 0); + return result; +} + +int NgHttp2Adapter::GetPeerConnectionWindow() const { + return session_->GetRemoteWindowSize(); +} + +void NgHttp2Adapter::MarkDataConsumedForStream(Http2StreamId stream_id, + size_t num_bytes) { + int rc = session_->Consume(stream_id, num_bytes); + if (rc != 0) { + QUICHE_LOG(ERROR) << "Error " << rc << " marking " << num_bytes + << " bytes consumed for stream " << stream_id; + } +} + +void NgHttp2Adapter::SubmitRst(Http2StreamId stream_id, + Http2ErrorCode error_code) { + int status = + nghttp2_submit_rst_stream(session_->raw_ptr(), NGHTTP2_FLAG_NONE, + stream_id, static_cast<uint32_t>(error_code)); + if (status < 0) { + QUICHE_LOG(WARNING) << "Reset stream failed: " << stream_id + << " with status code " << status; + } +} + +NgHttp2Adapter::NgHttp2Adapter(Http2VisitorInterface& visitor, + Perspective perspective) + : Http2Adapter(visitor), visitor_(visitor), perspective_(perspective) {} + +NgHttp2Adapter::~NgHttp2Adapter() {} + +void NgHttp2Adapter::Initialize() { + nghttp2_option* options; + nghttp2_option_new(&options); + // Set some common options for compatibility. + nghttp2_option_set_no_closed_streams(options, 1); + nghttp2_option_set_no_auto_window_update(options, 1); + nghttp2_option_set_max_send_header_block_length(options, 0x2000000); + nghttp2_option_set_max_outbound_ack(options, 10000); + + session_ = + absl::make_unique<NgHttp2Session>(perspective_, callbacks::Create(), + options, static_cast<void*>(&visitor_)); +} + +} // namespace adapter +} // namespace http2
diff --git a/http2/adapter/nghttp2_adapter.h b/http2/adapter/nghttp2_adapter.h new file mode 100644 index 0000000..13c2ffc --- /dev/null +++ b/http2/adapter/nghttp2_adapter.h
@@ -0,0 +1,97 @@ +#ifndef QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_ +#define QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_ + +#include "http2/adapter/http2_adapter.h" +#include "http2/adapter/http2_protocol.h" +#include "http2/adapter/nghttp2_session.h" +#include "http2/adapter/nghttp2_util.h" + +namespace http2 { +namespace adapter { + +class NgHttp2Adapter : public Http2Adapter { + public: + ~NgHttp2Adapter() override; + + // Creates an adapter that functions as a client. + static std::unique_ptr<NgHttp2Adapter> CreateClientAdapter( + Http2VisitorInterface& visitor); + + // Creates an adapter that functions as a server. + static std::unique_ptr<NgHttp2Adapter> CreateServerAdapter( + Http2VisitorInterface& visitor); + + // Processes the incoming |bytes| as HTTP/2 and invokes callbacks on the + // |visitor_| as appropriate. + ssize_t ProcessBytes(absl::string_view bytes) override; + + // Submits the |settings| to be written to the peer, e.g., as part of the + // HTTP/2 connection preface. + void SubmitSettings(absl::Span<const Http2Setting> settings) override; + + // Submits a PRIORITY frame for the given stream. + void SubmitPriorityForStream(Http2StreamId stream_id, + Http2StreamId parent_stream_id, + int weight, + bool exclusive) override; + + // Submits a PING on the connection. Note that nghttp2 automatically submits + // PING acks upon receiving non-ack PINGs from the peer, so callers only use + // this method to originate PINGs. See nghttp2_option_set_no_auto_ping_ack(). + void SubmitPing(Http2PingId ping_id) override; + + // Submits a GOAWAY on the connection. Note that |last_accepted_stream_id| + // refers to stream IDs initiated by the peer. For client-side, this last + // stream ID must be even (or 0); for server-side, this last stream ID must be + // odd (or 0). + // TODO(birenroy): Add a graceful shutdown behavior to the API. + void SubmitGoAway(Http2StreamId last_accepted_stream_id, + Http2ErrorCode error_code, + absl::string_view opaque_data) override; + + // Submits a WINDOW_UPDATE for the given stream (a |stream_id| of 0 indicates + // a connection-level WINDOW_UPDATE). + void SubmitWindowUpdate(Http2StreamId stream_id, + int window_increment) override; + + // Submits a METADATA frame for the given stream (a |stream_id| of 0 indicates + // connection-level METADATA). If |end_metadata|, the frame will also have the + // END_METADATA flag set. + void SubmitMetadata(Http2StreamId stream_id, bool end_metadata) override; + + // Returns serialized bytes for writing to the wire. Writes should be + // submitted to Nghttp2Adapter first, so that Nghttp2Adapter has data to + // serialize and return in this method. + std::string GetBytesToWrite(absl::optional<size_t> max_bytes) override; + + // Returns the connection-level flow control window for the peer. + int GetPeerConnectionWindow() const override; + + // Marks the given amount of data as consumed for the given stream, which + // enables the nghttp2 layer to trigger WINDOW_UPDATEs as appropriate. + void MarkDataConsumedForStream(Http2StreamId stream_id, + size_t num_bytes) override; + + // Submits a RST_STREAM with the desired |error_code|. + void SubmitRst(Http2StreamId stream_id, Http2ErrorCode error_code) override; + + // TODO(b/181586191): Temporary accessor until equivalent functionality is + // available in this adapter class. + NgHttp2Session& session() { return *session_; } + + private: + NgHttp2Adapter(Http2VisitorInterface& visitor, Perspective perspective); + + // Performs any necessary initialization of the underlying HTTP/2 session, + // such as preparing initial SETTINGS. + void Initialize(); + + std::unique_ptr<NgHttp2Session> session_; + Http2VisitorInterface& visitor_; + Perspective perspective_; +}; + +} // namespace adapter +} // namespace http2 + +#endif // QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc new file mode 100644 index 0000000..d7e1fe6 --- /dev/null +++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -0,0 +1,252 @@ +#include "http2/adapter/nghttp2_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" + +namespace http2 { +namespace adapter { +namespace test { +namespace { + +using testing::_; + +enum FrameType { + DATA, + HEADERS, + PRIORITY, + RST_STREAM, + SETTINGS, + PUSH_PROMISE, + PING, + GOAWAY, + WINDOW_UPDATE, +}; + +TEST(NgHttp2AdapterTest, ClientConstruction) { + testing::StrictMock<MockHttp2Visitor> visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + ASSERT_NE(nullptr, adapter); + EXPECT_TRUE(adapter->session().want_read()); + EXPECT_FALSE(adapter->session().want_write()); +} + +TEST(NgHttp2AdapterTest, ClientHandlesFrames) { + testing::StrictMock<MockHttp2Visitor> visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + std::string serialized = adapter->GetBytesToWrite(absl::nullopt); + EXPECT_THAT(serialized, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); + + const std::string initial_frames = TestFrameSequence() + .ServerPreface() + .Ping(42) + .WindowUpdate(0, 1000) + .Serialize(); + testing::InSequence s; + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0)); + EXPECT_CALL(visitor, OnPing(42, false)); + EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 1000)); + + const ssize_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), initial_result); + + EXPECT_EQ(adapter->GetPeerConnectionWindow(), + kDefaultInitialStreamWindowSize + 1000); + // Some bytes should have been serialized. + serialized = adapter->GetBytesToWrite(absl::nullopt); + EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::PING})); + + const std::vector<Header> headers1 = {{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}; + const auto nvs1 = GetRequestNghttp2Nvs(headers1); + + const std::vector<Header> headers2 = {{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}; + const auto nvs2 = GetRequestNghttp2Nvs(headers2); + + const std::vector<Header> headers3 = {{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/three"}}; + const auto nvs3 = GetRequestNghttp2Nvs(headers3); + + const int32_t stream_id1 = + nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs1.data(), + nvs1.size(), nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + const int32_t stream_id2 = + nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs2.data(), + nvs2.size(), nullptr, nullptr); + ASSERT_GT(stream_id2, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id2; + + const int32_t stream_id3 = + nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs3.data(), + nvs3.size(), nullptr, nullptr); + ASSERT_GT(stream_id3, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id3; + + serialized = adapter->GetBytesToWrite(absl::nullopt); + EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::HEADERS})); + + 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, OnFrameHeader(1, _, HEADERS, 4)); + 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, OnFrameHeader(1, 26, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnFrameHeader(3, 4, RST_STREAM, 0)); + EXPECT_CALL(visitor, OnRstStream(3, Http2ErrorCode::INTERNAL_ERROR)); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::INTERNAL_ERROR)); + EXPECT_CALL(visitor, OnFrameHeader(0, 19, GOAWAY, 0)); + EXPECT_CALL(visitor, + OnGoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!")); + const ssize_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + // Even though the client recieved a GOAWAY, streams 1 and 5 are still active. + EXPECT_TRUE(adapter->session().want_read()); + + EXPECT_CALL(visitor, OnFrameHeader(1, 0, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 0)); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + EXPECT_CALL(visitor, OnFrameHeader(5, 4, RST_STREAM, 0)); + EXPECT_CALL(visitor, OnRstStream(5, Http2ErrorCode::REFUSED_STREAM)); + EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::REFUSED_STREAM)); + adapter->ProcessBytes(TestFrameSequence() + .Data(1, "", true) + .RstStream(5, Http2ErrorCode::REFUSED_STREAM) + .Serialize()); + // After receiving END_STREAM for 1 and RST_STREAM for 5, the session no + // longer expects reads. + EXPECT_FALSE(adapter->session().want_read()); + + // Client will not have anything else to write. + EXPECT_FALSE(adapter->session().want_write()); + serialized = adapter->GetBytesToWrite(absl::nullopt); + EXPECT_THAT(serialized, testing::IsEmpty()); +} + +TEST(NgHttp2AdapterTest, ServerConstruction) { + testing::StrictMock<MockHttp2Visitor> visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + ASSERT_NE(nullptr, adapter); + EXPECT_TRUE(adapter->session().want_read()); + EXPECT_FALSE(adapter->session().want_write()); +} + +TEST(NgHttp2AdapterTest, ServerHandlesFrames) { + testing::StrictMock<MockHttp2Visitor> visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + 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, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0)); + EXPECT_CALL(visitor, OnPing(42, false)); + EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 1000)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + 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, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); + EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 25)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body.")); + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + 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, OnFrameHeader(3, 4, RST_STREAM, 0)); + EXPECT_CALL(visitor, OnRstStream(3, Http2ErrorCode::CANCEL)); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::CANCEL)); + EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0)); + EXPECT_CALL(visitor, OnPing(47, false)); + + const ssize_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_EQ(adapter->GetPeerConnectionWindow(), + kDefaultInitialStreamWindowSize + 1000); + + EXPECT_TRUE(adapter->session().want_write()); + // Some bytes should have been serialized. + std::string serialized = adapter->GetBytesToWrite(absl::nullopt); + // SETTINGS ack, two PING acks. + EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::PING, + spdy::SpdyFrameType::PING})); +} + +} // namespace +} // namespace test +} // namespace adapter +} // namespace http2
diff --git a/http2/adapter/nghttp2_session.cc b/http2/adapter/nghttp2_session.cc index dec4c03..d434b06 100644 --- a/http2/adapter/nghttp2_session.cc +++ b/http2/adapter/nghttp2_session.cc
@@ -7,7 +7,9 @@ namespace { void DeleteOptions(nghttp2_option* options) { - nghttp2_option_del(options); + if (options) { + nghttp2_option_del(options); + } } } // namespace
diff --git a/http2/adapter/nghttp2_session.h b/http2/adapter/nghttp2_session.h index 5dc1ddf..d446a07 100644 --- a/http2/adapter/nghttp2_session.h +++ b/http2/adapter/nghttp2_session.h
@@ -11,6 +11,7 @@ // A C++ wrapper around common nghttp2_session operations. class NgHttp2Session : public Http2Session { public: + // Takes ownership of |options|. NgHttp2Session(Perspective perspective, nghttp2_session_callbacks_unique_ptr callbacks, nghttp2_option* options,
diff --git a/http2/adapter/nghttp2_session_test.cc b/http2/adapter/nghttp2_session_test.cc index 9491412..85f8922 100644 --- a/http2/adapter/nghttp2_session_test.cc +++ b/http2/adapter/nghttp2_session_test.cc
@@ -26,21 +26,10 @@ WINDOW_UPDATE, }; -class DataSavingVisitor : public testing::StrictMock<MockHttp2Visitor> { - public: - void Save(absl::string_view data) { absl::StrAppend(&data_, data); } - - const std::string& data() { return data_; } - void Clear() { data_.clear(); } - - private: - std::string data_; -}; - -ssize_t SaveSessionOutput(nghttp2_session* session, +ssize_t SaveSessionOutput(nghttp2_session* /* session*/, const uint8_t* data, size_t length, - int flags, + int /* flags */, void* user_data) { auto visitor = static_cast<DataSavingVisitor*>(user_data); visitor->Save(ToStringView(data, length));
diff --git a/http2/adapter/test_utils.h b/http2/adapter/test_utils.h index c899ac0..ef1ae29 100644 --- a/http2/adapter/test_utils.h +++ b/http2/adapter/test_utils.h
@@ -6,6 +6,7 @@ #include "absl/strings/string_view.h" #include "http2/adapter/http2_protocol.h" +#include "http2/adapter/mock_http2_visitor.h" #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" #include "common/platform/api/quiche_test.h" #include "spdy/core/spdy_protocol.h" @@ -14,6 +15,17 @@ namespace adapter { namespace test { +class DataSavingVisitor : public testing::StrictMock<MockHttp2Visitor> { + public: + void Save(absl::string_view data) { absl::StrAppend(&data_, data); } + + const std::string& data() { return data_; } + void Clear() { data_.clear(); } + + private: + std::string data_; +}; + // These matchers check whether a string consists entirely of HTTP/2 frames of // the specified ordered sequence. This is useful in tests where we want to show // that one or more particular frame types are serialized for sending to the