summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/http2/adapter
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/third_party/quiche/src/http2/adapter')
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc330
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h47
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc89
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/data_source.cc23
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/data_source.h53
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/data_source_test.cc40
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h106
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc4
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h46
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/http2_session.h5
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/http2_util.h7
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h63
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h46
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc9
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h6
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc174
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h81
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc1438
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc169
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h47
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.cc63
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.h37
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider_test.cc117
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc24
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h11
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc67
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test.cc205
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.cc454
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.h99
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc158
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h15
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util_test.cc109
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc77
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h31
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc841
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc722
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h193
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc842
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h4
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc58
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h20
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc76
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h20
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc526
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/test_utils.h115
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/window_manager.h4
-rw-r--r--chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc10
47 files changed, 6635 insertions, 1046 deletions
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc
index eaf4b6169d2..4381749d430 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc
@@ -36,14 +36,63 @@ struct nghttp2_session_callbacks {
namespace http2 {
namespace adapter {
+CallbackVisitor::CallbackVisitor(Perspective perspective,
+ const nghttp2_session_callbacks& callbacks,
+ void* user_data)
+ : perspective_(perspective),
+ callbacks_(MakeCallbacksPtr(nullptr)),
+ user_data_(user_data) {
+ nghttp2_session_callbacks* c;
+ nghttp2_session_callbacks_new(&c);
+ *c = callbacks;
+ callbacks_ = MakeCallbacksPtr(c);
+}
+
+ssize_t CallbackVisitor::OnReadyToSend(absl::string_view serialized) {
+ if (!callbacks_->send_callback) {
+ return kSendError;
+ }
+ ssize_t result = callbacks_->send_callback(
+ nullptr, ToUint8Ptr(serialized.data()), serialized.size(), 0, user_data_);
+ QUICHE_VLOG(1) << "CallbackVisitor::OnReadyToSend returning " << result;
+ if (result > 0) {
+ return result;
+ } else if (result == NGHTTP2_ERR_WOULDBLOCK) {
+ return kSendBlocked;
+ } else {
+ return kSendError;
+ }
+}
+
void CallbackVisitor::OnConnectionError() {
- QUICHE_LOG(FATAL) << "Not implemented";
+ QUICHE_LOG(ERROR) << "OnConnectionError not implemented";
}
void CallbackVisitor::OnFrameHeader(Http2StreamId stream_id,
size_t length,
uint8_t type,
uint8_t flags) {
+ QUICHE_VLOG(1) << "CallbackVisitor::OnFrameHeader(stream_id: " << stream_id
+ << ", len: " << length << ", type: " << int(type)
+ << ", flags: " << int(flags) << ")";
+ if (static_cast<FrameType>(type) == FrameType::CONTINUATION) {
+ // Treat CONTINUATION as HEADERS
+ QUICHE_DCHECK_EQ(current_frame_.hd.stream_id, stream_id);
+ current_frame_.hd.length += length;
+ current_frame_.hd.flags |= flags;
+ QUICHE_DLOG_IF(ERROR, length == 0) << "Empty CONTINUATION!";
+ // Still need to deliver the CONTINUATION to the begin frame callback.
+ nghttp2_frame_hd hd;
+ memset(&hd, 0, sizeof(hd));
+ hd.stream_id = stream_id;
+ hd.length = length;
+ hd.type = type;
+ hd.flags = flags;
+ if (callbacks_->on_begin_frame_callback) {
+ callbacks_->on_begin_frame_callback(nullptr, &hd, user_data_);
+ }
+ return;
+ }
// The general strategy is to clear |current_frame_| at the start of a new
// frame, accumulate frame information from the various callback events, then
// invoke the on_frame_recv_callback() with the accumulated frame data.
@@ -52,7 +101,10 @@ void CallbackVisitor::OnFrameHeader(Http2StreamId stream_id,
current_frame_.hd.length = length;
current_frame_.hd.type = type;
current_frame_.hd.flags = flags;
- callbacks_->on_begin_frame_callback(nullptr, &current_frame_.hd, user_data_);
+ if (callbacks_->on_begin_frame_callback) {
+ callbacks_->on_begin_frame_callback(nullptr, &current_frame_.hd,
+ user_data_);
+ }
}
void CallbackVisitor::OnSettingsStart() {}
@@ -64,106 +116,147 @@ void CallbackVisitor::OnSetting(Http2Setting setting) {
void CallbackVisitor::OnSettingsEnd() {
current_frame_.settings.niv = settings_.size();
current_frame_.settings.iv = settings_.data();
- callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ QUICHE_VLOG(1) << "OnSettingsEnd, received settings of size "
+ << current_frame_.settings.niv;
+ if (callbacks_->on_frame_recv_callback) {
+ callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ }
settings_.clear();
}
void CallbackVisitor::OnSettingsAck() {
// ACK is part of the flags, which were set in OnFrameHeader().
- callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ if (callbacks_->on_frame_recv_callback) {
+ callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ }
}
-void CallbackVisitor::OnBeginHeadersForStream(Http2StreamId stream_id) {
- auto it = stream_map_.find(stream_id);
- if (it == stream_map_.end()) {
- auto p = stream_map_.insert({stream_id, absl::make_unique<StreamInfo>()});
- it = p.first;
- }
+bool CallbackVisitor::OnBeginHeadersForStream(Http2StreamId stream_id) {
+ auto it = GetStreamInfo(stream_id);
if (it->second->received_headers) {
// At least one headers frame has already been received.
+ QUICHE_VLOG(1)
+ << "Headers already received for stream " << stream_id
+ << ", these are trailers or headers following a 100 response";
current_frame_.headers.cat = NGHTTP2_HCAT_HEADERS;
} else {
switch (perspective_) {
case Perspective::kClient:
+ QUICHE_VLOG(1) << "First headers at the client for stream " << stream_id
+ << "; these are response headers";
current_frame_.headers.cat = NGHTTP2_HCAT_RESPONSE;
break;
case Perspective::kServer:
+ QUICHE_VLOG(1) << "First headers at the server for stream " << stream_id
+ << "; these are request headers";
current_frame_.headers.cat = NGHTTP2_HCAT_REQUEST;
break;
}
}
- callbacks_->on_begin_headers_callback(nullptr, &current_frame_, user_data_);
it->second->received_headers = true;
+ if (callbacks_->on_begin_headers_callback) {
+ const int result = callbacks_->on_begin_headers_callback(
+ nullptr, &current_frame_, user_data_);
+ return result == 0;
+ }
+ return true;
}
-void CallbackVisitor::OnHeaderForStream(Http2StreamId stream_id,
- absl::string_view name,
- absl::string_view value) {
- callbacks_->on_header_callback(
- nullptr, &current_frame_, ToUint8Ptr(name.data()), name.size(),
- ToUint8Ptr(value.data()), value.size(), NGHTTP2_NV_FLAG_NONE, user_data_);
+Http2VisitorInterface::OnHeaderResult CallbackVisitor::OnHeaderForStream(
+ Http2StreamId /*stream_id*/, absl::string_view name,
+ absl::string_view value) {
+ if (callbacks_->on_header_callback) {
+ const int result = callbacks_->on_header_callback(
+ nullptr, &current_frame_, ToUint8Ptr(name.data()), name.size(),
+ ToUint8Ptr(value.data()), value.size(), NGHTTP2_NV_FLAG_NONE,
+ user_data_);
+ if (result == 0) {
+ return HEADER_OK;
+ } else if (result == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ return HEADER_RST_STREAM;
+ } else {
+ // Assume NGHTTP2_ERR_CALLBACK_FAILURE.
+ return HEADER_CONNECTION_ERROR;
+ }
+ }
+ return HEADER_OK;
}
-void CallbackVisitor::OnEndHeadersForStream(Http2StreamId stream_id) {
- callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+void CallbackVisitor::OnEndHeadersForStream(Http2StreamId /*stream_id*/) {
+ if (callbacks_->on_frame_recv_callback) {
+ const int result = callbacks_->on_frame_recv_callback(
+ nullptr, &current_frame_, user_data_);
+ QUICHE_DCHECK_EQ(0, result);
+ }
}
-void CallbackVisitor::OnBeginDataForStream(Http2StreamId stream_id,
+void CallbackVisitor::OnBeginDataForStream(Http2StreamId /*stream_id*/,
size_t payload_length) {
// TODO(b/181586191): Interpret padding, subtract padding from
// |remaining_data_|.
remaining_data_ = payload_length;
- if (remaining_data_ == 0) {
+ if (remaining_data_ == 0 && callbacks_->on_frame_recv_callback) {
callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
}
}
void CallbackVisitor::OnDataForStream(Http2StreamId stream_id,
absl::string_view data) {
- callbacks_->on_data_chunk_recv_callback(nullptr, current_frame_.hd.flags,
- stream_id, ToUint8Ptr(data.data()),
- data.size(), user_data_);
+ if (callbacks_->on_data_chunk_recv_callback) {
+ callbacks_->on_data_chunk_recv_callback(nullptr, current_frame_.hd.flags,
+ stream_id, ToUint8Ptr(data.data()),
+ data.size(), user_data_);
+ }
remaining_data_ -= data.size();
- if (remaining_data_ == 0) {
+ if (remaining_data_ == 0 && callbacks_->on_frame_recv_callback) {
callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
}
}
-void CallbackVisitor::OnEndStream(Http2StreamId stream_id) {}
+void CallbackVisitor::OnEndStream(Http2StreamId /*stream_id*/) {}
-void CallbackVisitor::OnRstStream(Http2StreamId stream_id,
+void CallbackVisitor::OnRstStream(Http2StreamId /*stream_id*/,
Http2ErrorCode error_code) {
current_frame_.rst_stream.error_code = static_cast<uint32_t>(error_code);
- callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ if (callbacks_->on_frame_recv_callback) {
+ callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ }
}
void CallbackVisitor::OnCloseStream(Http2StreamId stream_id,
Http2ErrorCode error_code) {
- callbacks_->on_stream_close_callback(
- nullptr, stream_id, static_cast<uint32_t>(error_code), user_data_);
+ if (callbacks_->on_stream_close_callback) {
+ QUICHE_VLOG(1) << "OnCloseStream(stream_id: " << stream_id
+ << ", error_code: " << int(error_code) << ")";
+ callbacks_->on_stream_close_callback(
+ nullptr, stream_id, static_cast<uint32_t>(error_code), user_data_);
+ }
}
-void CallbackVisitor::OnPriorityForStream(Http2StreamId stream_id,
+void CallbackVisitor::OnPriorityForStream(Http2StreamId /*stream_id*/,
Http2StreamId parent_stream_id,
- int weight,
- bool exclusive) {
+ int weight, bool exclusive) {
current_frame_.priority.pri_spec.stream_id = parent_stream_id;
current_frame_.priority.pri_spec.weight = weight;
current_frame_.priority.pri_spec.exclusive = exclusive;
- callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ if (callbacks_->on_frame_recv_callback) {
+ callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ }
}
-void CallbackVisitor::OnPing(Http2PingId ping_id, bool is_ack) {
+void CallbackVisitor::OnPing(Http2PingId ping_id, bool /*is_ack*/) {
uint64_t network_order_opaque_data =
quiche::QuicheEndian::HostToNet64(ping_id);
std::memcpy(current_frame_.ping.opaque_data, &network_order_opaque_data,
sizeof(network_order_opaque_data));
- callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ if (callbacks_->on_frame_recv_callback) {
+ callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ }
}
-void CallbackVisitor::OnPushPromiseForStream(Http2StreamId stream_id,
- Http2StreamId promised_stream_id) {
- QUICHE_LOG(FATAL) << "Not implemented";
+void CallbackVisitor::OnPushPromiseForStream(
+ Http2StreamId /*stream_id*/, Http2StreamId /*promised_stream_id*/) {
+ QUICHE_LOG(DFATAL) << "Not implemented";
}
void CallbackVisitor::OnGoAway(Http2StreamId last_accepted_stream_id,
@@ -173,42 +266,173 @@ void CallbackVisitor::OnGoAway(Http2StreamId last_accepted_stream_id,
current_frame_.goaway.error_code = static_cast<uint32_t>(error_code);
current_frame_.goaway.opaque_data = ToUint8Ptr(opaque_data.data());
current_frame_.goaway.opaque_data_len = opaque_data.size();
- callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ if (callbacks_->on_frame_recv_callback) {
+ callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ }
}
-void CallbackVisitor::OnWindowUpdate(Http2StreamId stream_id,
+void CallbackVisitor::OnWindowUpdate(Http2StreamId /*stream_id*/,
int window_increment) {
current_frame_.window_update.window_size_increment = window_increment;
- callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ if (callbacks_->on_frame_recv_callback) {
+ callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ }
+}
+
+void CallbackVisitor::PopulateFrame(nghttp2_frame& frame, uint8_t frame_type,
+ Http2StreamId stream_id, size_t length,
+ uint8_t flags, uint32_t error_code,
+ bool sent_headers) {
+ frame.hd.type = frame_type;
+ frame.hd.stream_id = stream_id;
+ frame.hd.length = length;
+ frame.hd.flags = flags;
+ const FrameType frame_type_enum = static_cast<FrameType>(frame_type);
+ if (frame_type_enum == FrameType::HEADERS) {
+ if (sent_headers) {
+ frame.headers.cat = NGHTTP2_HCAT_HEADERS;
+ } else {
+ switch (perspective_) {
+ case Perspective::kClient:
+ QUICHE_VLOG(1) << "First headers sent by the client for stream "
+ << stream_id << "; these are request headers";
+ frame.headers.cat = NGHTTP2_HCAT_REQUEST;
+ break;
+ case Perspective::kServer:
+ QUICHE_VLOG(1) << "First headers sent by the server for stream "
+ << stream_id << "; these are response headers";
+ frame.headers.cat = NGHTTP2_HCAT_RESPONSE;
+ break;
+ }
+ }
+ } else if (frame_type_enum == FrameType::RST_STREAM) {
+ frame.rst_stream.error_code = error_code;
+ } else if (frame_type_enum == FrameType::GOAWAY) {
+ frame.goaway.error_code = error_code;
+ }
}
-void CallbackVisitor::OnReadyToSendDataForStream(Http2StreamId stream_id,
- char* destination_buffer,
- size_t length,
- ssize_t* written,
- bool* end_stream) {
- QUICHE_LOG(FATAL) << "Not implemented";
+int CallbackVisitor::OnBeforeFrameSent(uint8_t frame_type,
+ Http2StreamId stream_id, size_t length,
+ uint8_t flags) {
+ if (callbacks_->before_frame_send_callback) {
+ QUICHE_VLOG(1) << "OnBeforeFrameSent(type=" << int(frame_type)
+ << ", stream_id=" << stream_id << ", length=" << length
+ << ", flags=" << int(flags) << ")";
+ nghttp2_frame frame;
+ auto it = GetStreamInfo(stream_id);
+ // The implementation of the before_frame_send_callback doesn't look at the
+ // error code, so for now it's populated with 0.
+ PopulateFrame(frame, frame_type, stream_id, length, flags, /*error_code=*/0,
+ it->second->before_sent_headers);
+ it->second->before_sent_headers = true;
+ return callbacks_->before_frame_send_callback(nullptr, &frame, user_data_);
+ }
+ return 0;
+}
+
+int CallbackVisitor::OnFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+ size_t length, uint8_t flags,
+ uint32_t error_code) {
+ if (callbacks_->on_frame_send_callback) {
+ QUICHE_VLOG(1) << "OnFrameSent(type=" << int(frame_type)
+ << ", stream_id=" << stream_id << ", length=" << length
+ << ", flags=" << int(flags) << ", error_code=" << error_code
+ << ")";
+ nghttp2_frame frame;
+ auto it = GetStreamInfo(stream_id);
+ PopulateFrame(frame, frame_type, stream_id, length, flags, error_code,
+ it->second->sent_headers);
+ it->second->sent_headers = true;
+ return callbacks_->on_frame_send_callback(nullptr, &frame, user_data_);
+ }
+ return 0;
+}
+
+void CallbackVisitor::OnReadyToSendDataForStream(Http2StreamId /*stream_id*/,
+ char* /*destination_buffer*/,
+ size_t /*length*/,
+ ssize_t* /*written*/,
+ bool* /*end_stream*/) {
+ QUICHE_LOG(DFATAL) << "Not implemented";
+}
+
+bool CallbackVisitor::OnInvalidFrame(Http2StreamId stream_id, int error_code) {
+ QUICHE_VLOG(1) << "OnInvalidFrame(" << stream_id << ", " << error_code << ")";
+ QUICHE_DCHECK_EQ(stream_id, current_frame_.hd.stream_id);
+ if (callbacks_->on_invalid_frame_recv_callback) {
+ return 0 == callbacks_->on_invalid_frame_recv_callback(
+ nullptr, &current_frame_, error_code, user_data_);
+ }
+ return true;
}
void CallbackVisitor::OnReadyToSendMetadataForStream(Http2StreamId stream_id,
char* buffer,
size_t length,
ssize_t* written) {
- QUICHE_LOG(FATAL) << "Not implemented";
+ if (callbacks_->pack_extension_callback) {
+ nghttp2_frame frame;
+ frame.hd.type = kMetadataFrameType;
+ frame.hd.stream_id = stream_id;
+ frame.hd.flags = 0;
+ frame.hd.length = 0;
+ *written = callbacks_->pack_extension_callback(nullptr, ToUint8Ptr(buffer),
+ length, &frame, user_data_);
+ }
+ QUICHE_VLOG(1) << "OnReadyToSendMetadataForStream(stream_id=" << stream_id
+ << ", length=" << length << ", written=" << *written << ")";
}
void CallbackVisitor::OnBeginMetadataForStream(Http2StreamId stream_id,
size_t payload_length) {
- QUICHE_LOG(FATAL) << "Not implemented";
+ QUICHE_VLOG(1) << "OnBeginMetadataForStream(stream_id=" << stream_id
+ << ", payload_length=" << payload_length << ")";
}
void CallbackVisitor::OnMetadataForStream(Http2StreamId stream_id,
absl::string_view metadata) {
- QUICHE_LOG(FATAL) << "Not implemented";
+ QUICHE_VLOG(1) << "OnMetadataForStream(stream_id=" << stream_id
+ << ", len=" << metadata.size() << ")";
+ if (callbacks_->on_extension_chunk_recv_callback) {
+ int result = callbacks_->on_extension_chunk_recv_callback(
+ nullptr, &current_frame_.hd, ToUint8Ptr(metadata.data()),
+ metadata.size(), user_data_);
+ QUICHE_DCHECK_EQ(0, result);
+ }
+}
+
+bool CallbackVisitor::OnMetadataEndForStream(Http2StreamId stream_id) {
+ QUICHE_LOG_IF(DFATAL, current_frame_.hd.flags != kMetadataEndFlag);
+ QUICHE_VLOG(1) << "OnMetadataEndForStream(stream_id=" << stream_id << ")";
+ if (callbacks_->unpack_extension_callback) {
+ void* payload;
+ int result = callbacks_->unpack_extension_callback(
+ nullptr, &payload, &current_frame_.hd, user_data_);
+ if (callbacks_->on_frame_recv_callback) {
+ current_frame_.ext.payload = payload;
+ callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+ }
+ return (result == 0);
+ }
+ return true;
}
-void CallbackVisitor::OnMetadataEndForStream(Http2StreamId stream_id) {
- QUICHE_LOG(FATAL) << "Not implemented";
+void CallbackVisitor::OnErrorDebug(absl::string_view message) {
+ if (callbacks_->error_callback2) {
+ callbacks_->error_callback2(nullptr, -1, message.data(), message.size(),
+ user_data_);
+ }
+}
+
+CallbackVisitor::StreamInfoMap::iterator CallbackVisitor::GetStreamInfo(
+ Http2StreamId stream_id) {
+ auto it = stream_map_.find(stream_id);
+ if (it == stream_map_.end()) {
+ auto p = stream_map_.insert({stream_id, absl::make_unique<StreamInfo>()});
+ it = p.first;
+ }
+ return it;
}
} // namespace adapter
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h
index 94746bf8edb..b5afc83a103 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h
@@ -8,21 +8,20 @@
#include "http2/adapter/http2_visitor_interface.h"
#include "http2/adapter/nghttp2_util.h"
#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
// This visitor implementation accepts a set of nghttp2 callbacks and a "user
// data" pointer, and invokes the callbacks according to HTTP/2 events received.
-class CallbackVisitor : public Http2VisitorInterface {
+class QUICHE_EXPORT_PRIVATE CallbackVisitor : public Http2VisitorInterface {
public:
explicit CallbackVisitor(Perspective perspective,
- nghttp2_session_callbacks_unique_ptr callbacks,
- void* user_data)
- : perspective_(perspective),
- callbacks_(std::move(callbacks)),
- user_data_(user_data) {}
+ const nghttp2_session_callbacks& callbacks,
+ void* user_data);
+ ssize_t OnReadyToSend(absl::string_view serialized) override;
void OnConnectionError() override;
void OnFrameHeader(Http2StreamId stream_id,
size_t length,
@@ -32,10 +31,10 @@ class CallbackVisitor : public Http2VisitorInterface {
void OnSetting(Http2Setting setting) override;
void OnSettingsEnd() override;
void OnSettingsAck() override;
- void OnBeginHeadersForStream(Http2StreamId stream_id) override;
- void OnHeaderForStream(Http2StreamId stream_id,
- absl::string_view name,
- absl::string_view value) override;
+ bool OnBeginHeadersForStream(Http2StreamId stream_id) override;
+ OnHeaderResult OnHeaderForStream(Http2StreamId stream_id,
+ absl::string_view name,
+ absl::string_view value) override;
void OnEndHeadersForStream(Http2StreamId stream_id) override;
void OnBeginDataForStream(Http2StreamId stream_id,
size_t payload_length) override;
@@ -56,11 +55,16 @@ class CallbackVisitor : public Http2VisitorInterface {
Http2ErrorCode error_code,
absl::string_view opaque_data) override;
void OnWindowUpdate(Http2StreamId stream_id, int window_increment) override;
+ int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+ size_t length, uint8_t flags) override;
+ int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id, size_t length,
+ uint8_t flags, uint32_t error_code) override;
void OnReadyToSendDataForStream(Http2StreamId stream_id,
char* destination_buffer,
size_t length,
ssize_t* written,
bool* end_stream) override;
+ bool OnInvalidFrame(Http2StreamId stream_id, int error_code) override;
void OnReadyToSendMetadataForStream(Http2StreamId stream_id,
char* buffer,
size_t length,
@@ -69,9 +73,25 @@ class CallbackVisitor : public Http2VisitorInterface {
size_t payload_length) override;
void OnMetadataForStream(Http2StreamId stream_id,
absl::string_view metadata) override;
- void OnMetadataEndForStream(Http2StreamId stream_id) override;
+ bool OnMetadataEndForStream(Http2StreamId stream_id) override;
+ void OnErrorDebug(absl::string_view message) override;
private:
+ struct QUICHE_EXPORT_PRIVATE StreamInfo {
+ bool before_sent_headers = false;
+ bool sent_headers = false;
+ bool received_headers = false;
+ };
+
+ using StreamInfoMap =
+ absl::flat_hash_map<Http2StreamId, std::unique_ptr<StreamInfo>>;
+
+ void PopulateFrame(nghttp2_frame& frame, uint8_t frame_type,
+ Http2StreamId stream_id, size_t length, uint8_t flags,
+ uint32_t error_code, bool sent_headers);
+ // Creates the StreamInfoMap entry if it doesn't exist.
+ StreamInfoMap::iterator GetStreamInfo(Http2StreamId stream_id);
+
Perspective perspective_;
nghttp2_session_callbacks_unique_ptr callbacks_;
void* user_data_;
@@ -80,10 +100,7 @@ class CallbackVisitor : public Http2VisitorInterface {
std::vector<nghttp2_settings_entry> settings_;
size_t remaining_data_ = 0;
- struct StreamInfo {
- bool received_headers = false;
- };
- absl::flat_hash_map<Http2StreamId, std::unique_ptr<StreamInfo>> stream_map_;
+ StreamInfoMap stream_map_;
};
} // namespace adapter
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc
index 4dfcb5fcddb..2cb31ed24f4 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc
@@ -1,6 +1,7 @@
#include "http2/adapter/callback_visitor.h"
#include "http2/adapter/mock_nghttp2_callbacks.h"
+#include "http2/adapter/nghttp2_test_utils.h"
#include "http2/adapter/test_utils.h"
#include "common/platform/api/quiche_test.h"
@@ -21,13 +22,14 @@ enum FrameType {
PING,
GOAWAY,
WINDOW_UPDATE,
+ CONTINUATION,
};
// Tests connection-level events.
TEST(ClientCallbackVisitorUnitTest, ConnectionFrames) {
testing::StrictMock<MockNghttp2Callbacks> callbacks;
CallbackVisitor visitor(Perspective::kClient,
- MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+ *MockNghttp2Callbacks::GetCallbacks(), &callbacks);
testing::InSequence seq;
@@ -73,7 +75,7 @@ TEST(ClientCallbackVisitorUnitTest, ConnectionFrames) {
TEST(ClientCallbackVisitorUnitTest, StreamFrames) {
testing::StrictMock<MockNghttp2Callbacks> callbacks;
CallbackVisitor visitor(Perspective::kClient,
- MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+ *MockNghttp2Callbacks::GetCallbacks(), &callbacks);
testing::InSequence seq;
@@ -153,12 +155,69 @@ TEST(ClientCallbackVisitorUnitTest, StreamFrames) {
EXPECT_CALL(callbacks, OnStreamClose(5, NGHTTP2_REFUSED_STREAM));
visitor.OnCloseStream(5, Http2ErrorCode::REFUSED_STREAM);
+
+ // Metadata events
+ constexpr size_t kMetadataBufferSize = 256;
+ char metadata_dest[kMetadataBufferSize];
+ ssize_t written = 0;
+
+ const absl::string_view kExampleFrame = "This is supposed to be metadata.";
+ EXPECT_CALL(
+ callbacks,
+ OnPackExtension(_, kMetadataBufferSize,
+ Field(&nghttp2_frame::hd,
+ HasFrameHeaderRef(7, kMetadataFrameType, _))))
+ .WillOnce(testing::Invoke(
+ [kExampleFrame](uint8_t* buf, size_t /*len*/,
+ const nghttp2_frame* /*frame*/) -> ssize_t {
+ std::memcpy(buf, kExampleFrame.data(), kExampleFrame.size());
+ return kExampleFrame.size();
+ }));
+ visitor.OnReadyToSendMetadataForStream(7, metadata_dest, kMetadataBufferSize,
+ &written);
+ ASSERT_EQ(written, kExampleFrame.size());
+ EXPECT_EQ(absl::string_view(metadata_dest, written), kExampleFrame);
+}
+
+TEST(ClientCallbackVisitorUnitTest, HeadersWithContinuation) {
+ testing::StrictMock<MockNghttp2Callbacks> callbacks;
+ CallbackVisitor visitor(Perspective::kClient,
+ *MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+
+ testing::InSequence seq;
+
+ // HEADERS on stream 1
+ EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, HEADERS, 0x0)));
+ visitor.OnFrameHeader(1, 23, HEADERS, 0x0);
+
+ EXPECT_CALL(callbacks,
+ OnBeginHeaders(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE)));
+ visitor.OnBeginHeadersForStream(1);
+
+ EXPECT_CALL(callbacks, OnHeader(_, ":status", "200", _));
+ visitor.OnHeaderForStream(1, ":status", "200");
+
+ EXPECT_CALL(callbacks, OnHeader(_, "server", "my-fake-server", _));
+ visitor.OnHeaderForStream(1, "server", "my-fake-server");
+
+ EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, CONTINUATION, 0x4)));
+ visitor.OnFrameHeader(1, 23, CONTINUATION, 0x4);
+
+ EXPECT_CALL(callbacks,
+ OnHeader(_, "date", "Tue, 6 Apr 2021 12:54:01 GMT", _));
+ visitor.OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT");
+
+ EXPECT_CALL(callbacks, OnHeader(_, "trailer", "x-server-status", _));
+ visitor.OnHeaderForStream(1, "trailer", "x-server-status");
+
+ EXPECT_CALL(callbacks, OnFrameRecv(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE)));
+ visitor.OnEndHeadersForStream(1);
}
TEST(ServerCallbackVisitorUnitTest, ConnectionFrames) {
testing::StrictMock<MockNghttp2Callbacks> callbacks;
CallbackVisitor visitor(Perspective::kServer,
- MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+ *MockNghttp2Callbacks::GetCallbacks(), &callbacks);
testing::InSequence seq;
@@ -196,7 +255,7 @@ TEST(ServerCallbackVisitorUnitTest, ConnectionFrames) {
TEST(ServerCallbackVisitorUnitTest, StreamFrames) {
testing::StrictMock<MockNghttp2Callbacks> callbacks;
CallbackVisitor visitor(Perspective::kServer,
- MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+ *MockNghttp2Callbacks::GetCallbacks(), &callbacks);
testing::InSequence seq;
@@ -252,6 +311,28 @@ TEST(ServerCallbackVisitorUnitTest, StreamFrames) {
EXPECT_CALL(callbacks, OnStreamClose(3, NGHTTP2_INTERNAL_ERROR));
visitor.OnCloseStream(3, Http2ErrorCode::INTERNAL_ERROR);
+
+ // Metadata events
+ constexpr size_t kMetadataBufferSize = 256;
+ char metadata_dest[kMetadataBufferSize];
+ ssize_t written = 0;
+
+ const absl::string_view kExampleFrame = "This is supposed to be metadata.";
+ EXPECT_CALL(
+ callbacks,
+ OnPackExtension(_, kMetadataBufferSize,
+ Field(&nghttp2_frame::hd,
+ HasFrameHeaderRef(5, kMetadataFrameType, _))))
+ .WillOnce(testing::Invoke(
+ [kExampleFrame](uint8_t* buf, size_t /*len*/,
+ const nghttp2_frame* /*frame*/) -> ssize_t {
+ std::memcpy(buf, kExampleFrame.data(), kExampleFrame.size());
+ return kExampleFrame.size();
+ }));
+ visitor.OnReadyToSendMetadataForStream(5, metadata_dest, kMetadataBufferSize,
+ &written);
+ ASSERT_EQ(written, kExampleFrame.size());
+ EXPECT_EQ(absl::string_view(metadata_dest, written), kExampleFrame);
}
} // namespace
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/data_source.cc b/chromium/net/third_party/quiche/src/http2/adapter/data_source.cc
deleted file mode 100644
index 13f89d28581..00000000000
--- a/chromium/net/third_party/quiche/src/http2/adapter/data_source.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-#include "http2/adapter/data_source.h"
-
-namespace http2 {
-namespace adapter {
-
-StringDataSource::StringDataSource(std::string data)
- : data_(std::move(data)), remaining_(data_) {
- state_ = remaining_.empty() ? DONE : READY;
-}
-
-absl::string_view StringDataSource::NextData() const {
- return remaining_;
-}
-
-void StringDataSource::Consume(size_t bytes) {
- remaining_.remove_prefix(std::min(bytes, remaining_.size()));
- if (remaining_.empty()) {
- state_ = DONE;
- }
-}
-
-} // namespace adapter
-} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/data_source.h b/chromium/net/third_party/quiche/src/http2/adapter/data_source.h
index e170104a597..d86714148c3 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/data_source.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/data_source.h
@@ -2,49 +2,46 @@
#define QUICHE_HTTP2_ADAPTER_DATA_SOURCE_H_
#include <string>
+#include <utility>
#include "absl/strings/string_view.h"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
-// Represents a HTTP message body.
-class DataSource {
+// Represents a source of DATA frames for transmission to the peer.
+class QUICHE_EXPORT_PRIVATE DataFrameSource {
public:
- virtual ~DataSource() {}
+ virtual ~DataFrameSource() {}
- enum State {
- // The source is not done, but cannot currently provide more data.
- NOT_READY,
- // The source can provide more data.
- READY,
- // The source is done.
- DONE,
- };
+ enum : ssize_t { kBlocked = 0, kError = -1 };
- State state() const { return state_; }
+ // Returns the number of bytes to send in the next DATA frame, and whether
+ // this frame indicates the end of the data. Returns {kBlocked, false} if
+ // blocked, {kError, false} on error.
+ virtual std::pair<ssize_t, bool> SelectPayloadLength(size_t max_length) = 0;
- // The next range of data provided by this data source.
- virtual absl::string_view NextData() const = 0;
+ // This method is called with a frame header and a payload length to send. The
+ // source should send or buffer the entire frame and return true, or return
+ // false without sending or buffering anything.
+ virtual bool Send(absl::string_view frame_header, size_t payload_length) = 0;
- // Indicates that |bytes| bytes have been consumed by the caller.
- virtual void Consume(size_t bytes) = 0;
-
- protected:
- State state_ = NOT_READY;
+ // If true, the end of this data source indicates the end of the stream.
+ // Otherwise, this data will be followed by trailers.
+ virtual bool send_fin() const = 0;
};
-// A simple implementation constructible from a string_view or std::string.
-class StringDataSource : public DataSource {
+// Represents a source of metadata frames for transmission to the peer.
+class QUICHE_EXPORT_PRIVATE MetadataSource {
public:
- explicit StringDataSource(std::string data);
-
- absl::string_view NextData() const override;
- void Consume(size_t bytes) override;
+ virtual ~MetadataSource() {}
- private:
- const std::string data_;
- absl::string_view remaining_;
+ // This method is called with a destination buffer and length. It should
+ // return the number of payload bytes copied to |dest|, or a negative integer
+ // to indicate an error, as well as a boolean indicating whether the metadata
+ // has been completely copied.
+ virtual std::pair<ssize_t, bool> Pack(uint8_t* dest, size_t dest_len) = 0;
};
} // namespace adapter
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/data_source_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/data_source_test.cc
deleted file mode 100644
index c290124b430..00000000000
--- a/chromium/net/third_party/quiche/src/http2/adapter/data_source_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-#include "http2/adapter/data_source.h"
-
-#include "common/platform/api/quiche_test.h"
-
-namespace http2 {
-namespace adapter {
-namespace test {
-namespace {
-
-TEST(StringDataSourceTest, EmptyString) {
- StringDataSource source("");
-
- EXPECT_EQ(source.state(), DataSource::DONE);
- EXPECT_THAT(source.NextData(), testing::IsEmpty());
-}
-
-TEST(StringDataSourceTest, PartialConsume) {
- StringDataSource source("I'm a HTTP message body. Really!");
-
- EXPECT_EQ(source.state(), DataSource::READY);
- EXPECT_THAT(source.NextData(), testing::Not(testing::IsEmpty()));
- source.Consume(6);
- EXPECT_EQ(source.state(), DataSource::READY);
- EXPECT_THAT(source.NextData(), testing::StartsWith("HTTP"));
-
- source.Consume(0);
- EXPECT_EQ(source.state(), DataSource::READY);
- EXPECT_THAT(source.NextData(), testing::StartsWith("HTTP"));
-
- // Consumes more than the remaining bytes available.
- source.Consume(50);
- EXPECT_THAT(source.NextData(), testing::IsEmpty())
- << "next data: " << source.NextData();
- EXPECT_EQ(source.state(), DataSource::DONE);
-}
-
-} // namespace
-} // namespace test
-} // namespace adapter
-} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h
index ef2ae0a5baf..789be98ae3b 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h
@@ -4,9 +4,11 @@
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/span.h"
+#include "http2/adapter/data_source.h"
#include "http2/adapter/http2_protocol.h"
#include "http2/adapter/http2_session.h"
#include "http2/adapter/http2_visitor_interface.h"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
@@ -17,11 +19,13 @@ namespace adapter {
// invokes corresponding callbacks on its passed-in Http2VisitorInterface.
// Http2Adapter is a base class shared between client-side and server-side
// implementations.
-class Http2Adapter {
+class QUICHE_EXPORT_PRIVATE Http2Adapter {
public:
Http2Adapter(const Http2Adapter&) = delete;
Http2Adapter& operator=(const Http2Adapter&) = delete;
+ virtual bool IsServerSession() const = 0;
+
// Processes the incoming |bytes| as HTTP/2 and invokes callbacks on the
// |visitor_| as appropriate.
virtual ssize_t ProcessBytes(absl::string_view bytes) = 0;
@@ -36,17 +40,15 @@ class Http2Adapter {
int weight,
bool exclusive) = 0;
- // 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().
+ // Submits a PING on the connection.
virtual void SubmitPing(Http2PingId ping_id) = 0;
+ // Starts a graceful shutdown. A no-op for clients.
+ virtual void SubmitShutdownNotice() = 0;
+
// 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). To submit a GOAWAY with |last_accepted_stream_id| with the
- // maximum stream ID, signaling imminent connection termination, call
- // SubmitShutdownNotice() instead (though this is only possible server-side).
+ // refers to stream IDs initiated by the peer. For a server sending this
+ // frame, this last stream ID must be odd (or 0).
virtual void SubmitGoAway(Http2StreamId last_accepted_stream_id,
Http2ErrorCode error_code,
absl::string_view opaque_data) = 0;
@@ -56,27 +58,85 @@ class Http2Adapter {
virtual void SubmitWindowUpdate(Http2StreamId stream_id,
int window_increment) = 0;
- // Submits a METADATA frame for the given stream (a |stream_id| of 0 indicates
- // connection-level METADATA). If |fin|, the frame will also have the
- // END_METADATA flag set.
- virtual void SubmitMetadata(Http2StreamId stream_id, bool fin) = 0;
+ // Submits a RST_STREAM for the given |stream_id| and |error_code|.
+ virtual void SubmitRst(Http2StreamId stream_id,
+ Http2ErrorCode error_code) = 0;
+
+ // Submits a sequence of METADATA frames for the given stream. A |stream_id|
+ // of 0 indicates connection-level METADATA.
+ virtual void SubmitMetadata(Http2StreamId stream_id,
+ std::unique_ptr<MetadataSource> source) = 0;
+
+ // Invokes the visitor's OnReadyToSend() method for serialized frame data.
+ // Returns 0 on success.
+ virtual int Send() = 0;
+
+ // Returns the connection-level flow control window advertised by the peer.
+ virtual int GetSendWindowSize() const = 0;
- // Returns serialized bytes for writing to the wire.
- // Writes should be submitted to Http2Adapter first, so that Http2Adapter
- // has data to serialize and return in this method.
- virtual std::string GetBytesToWrite(absl::optional<size_t> max_bytes) = 0;
+ // Returns the stream-level flow control window advertised by the peer.
+ virtual int GetStreamSendWindowSize(Http2StreamId stream_id) const = 0;
- // Returns the connection-level flow control window for the peer.
- virtual int GetPeerConnectionWindow() const = 0;
+ // Returns the current upper bound on the flow control receive window for this
+ // stream. This value does not account for data received from the peer.
+ virtual int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const = 0;
+
+ // Returns the amount of data a peer could send on a given stream. This is
+ // the outstanding stream receive window.
+ virtual int GetStreamReceiveWindowSize(Http2StreamId stream_id) const = 0;
+
+ // Returns the total amount of data a peer could send on the connection. This
+ // is the outstanding connection receive window.
+ virtual int GetReceiveWindowSize() const = 0;
+
+ // Returns the size of the HPACK encoder's dynamic table, including the
+ // per-entry overhead from the specification.
+ virtual int GetHpackEncoderDynamicTableSize() const = 0;
+
+ // Returns the size of the HPACK decoder's dynamic table, including the
+ // per-entry overhead from the specification.
+ virtual int GetHpackDecoderDynamicTableSize() const = 0;
+
+ // Gets the highest stream ID value seen in a frame received by this endpoint.
+ // This method is only guaranteed to work for server endpoints.
+ virtual Http2StreamId GetHighestReceivedStreamId() const = 0;
// Marks the given amount of data as consumed for the given stream, which
- // enables the nghttp2 layer to trigger WINDOW_UPDATEs as appropriate.
+ // enables the implementation layer to send WINDOW_UPDATEs as appropriate.
virtual void MarkDataConsumedForStream(Http2StreamId stream_id,
size_t num_bytes) = 0;
- // Submits a RST_STREAM for the given stream.
- virtual void SubmitRst(Http2StreamId stream_id,
- Http2ErrorCode error_code) = 0;
+ // Returns the assigned stream ID if the operation succeeds. Otherwise,
+ // returns a negative integer indicating an error code. |data_source| may be
+ // nullptr if the request does not have a body.
+ virtual int32_t SubmitRequest(absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source,
+ void* user_data) = 0;
+
+ // Returns 0 on success. |data_source| may be nullptr if the response does not
+ // have a body.
+ virtual int SubmitResponse(Http2StreamId stream_id,
+ absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source) = 0;
+
+ // Queues trailers to be sent after any outstanding data on the stream with ID
+ // |stream_id|. Returns 0 on success.
+ virtual int SubmitTrailer(Http2StreamId stream_id,
+ absl::Span<const Header> trailers) = 0;
+
+ // Sets a user data pointer for the given stream. Can be called after
+ // SubmitRequest/SubmitResponse, or after receiving any frame for a given
+ // stream.
+ virtual void SetStreamUserData(Http2StreamId stream_id, void* user_data) = 0;
+
+ // Returns nullptr if the stream does not exist, or if stream user data has
+ // not been set.
+ virtual void* GetStreamUserData(Http2StreamId stream_id) = 0;
+
+ // Resumes a stream that was previously blocked (for example, due to
+ // DataFrameSource::SelectPayloadLength() returning kBlocked). Returns true if
+ // the stream was successfully resumed.
+ virtual bool ResumeStream(Http2StreamId stream_id) = 0;
protected:
// Subclasses should expose a public factory method for constructing and
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc b/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc
index 8d59aeedda0..dc0a57acac5 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc
@@ -12,6 +12,10 @@ const char kHttp2AuthorityPseudoHeader[] = ":authority";
const char kHttp2PathPseudoHeader[] = ":path";
const char kHttp2StatusPseudoHeader[] = ":status";
+const uint8_t kMetadataFrameType = 0x4d;
+const uint8_t kMetadataEndFlag = 0x04;
+const uint16_t kMetadataExtensionId = 0x4d44;
+
std::pair<absl::string_view, bool> GetStringView(const HeaderRep& rep) {
if (absl::holds_alternative<absl::string_view>(rep)) {
return std::make_pair(absl::get<absl::string_view>(rep), true);
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h
index 1e1dd39a21a..9f72fbe9d05 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h
@@ -5,10 +5,10 @@
#include <string>
#include <utility>
-#include "base/integral_types.h"
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "absl/types/variant.h"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
@@ -34,7 +34,7 @@ std::pair<absl::string_view, bool> GetStringView(const HeaderRep& rep);
using Header = std::pair<HeaderRep, HeaderRep>;
// Represents an HTTP/2 SETTINGS key-value parameter.
-struct Http2Setting {
+struct QUICHE_EXPORT_PRIVATE Http2Setting {
Http2SettingsId id;
uint32_t value;
};
@@ -50,17 +50,40 @@ const Http2StreamId kConnectionStreamId = 0;
// 7540 Section 6.5.2 (SETTINGS_MAX_FRAME_SIZE).
const int kDefaultFramePayloadSizeLimit = 16 * 1024;
-// The default value for the initial stream flow control window size, according
-// to RFC 7540 Section 6.9.2.
-const int kDefaultInitialStreamWindowSize = 64 * 1024 - 1;
+// The default value for the initial stream and connection flow control window
+// size, according to RFC 7540 Section 6.9.2.
+const int kInitialFlowControlWindowSize = 64 * 1024 - 1;
// The pseudo-header fields as specified in RFC 7540 Section 8.1.2.3 (request)
// and Section 8.1.2.4 (response).
-ABSL_CONST_INIT extern const char kHttp2MethodPseudoHeader[];
-ABSL_CONST_INIT extern const char kHttp2SchemePseudoHeader[];
-ABSL_CONST_INIT extern const char kHttp2AuthorityPseudoHeader[];
-ABSL_CONST_INIT extern const char kHttp2PathPseudoHeader[];
-ABSL_CONST_INIT extern const char kHttp2StatusPseudoHeader[];
+ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char
+ kHttp2MethodPseudoHeader[];
+ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char
+ kHttp2SchemePseudoHeader[];
+ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char
+ kHttp2AuthorityPseudoHeader[];
+ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char
+ kHttp2PathPseudoHeader[];
+ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char
+ kHttp2StatusPseudoHeader[];
+
+ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const uint8_t kMetadataFrameType;
+ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const uint8_t kMetadataEndFlag;
+ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const uint16_t
+ kMetadataExtensionId;
+
+enum class FrameType : uint8_t {
+ DATA = 0x0,
+ HEADERS,
+ PRIORITY,
+ RST_STREAM,
+ SETTINGS,
+ PUSH_PROMISE,
+ PING,
+ GOAWAY,
+ WINDOW_UPDATE,
+ CONTINUATION,
+};
// HTTP/2 error codes as specified in RFC 7540 Section 7.
enum class Http2ErrorCode {
@@ -94,7 +117,8 @@ enum Http2KnownSettingsId : Http2SettingsId {
INITIAL_WINDOW_SIZE = 0x4,
MAX_FRAME_SIZE = 0x5,
MAX_HEADER_LIST_SIZE = 0x6,
- MAX_SETTING = MAX_HEADER_LIST_SIZE
+ ENABLE_CONNECT_PROTOCOL = 0x8, // See RFC 8441
+ MAX_SETTING = ENABLE_CONNECT_PROTOCOL
};
// Returns a human-readable string representation of the given SETTINGS |id| for
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_session.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_session.h
index 0a6321c0ccc..3b57d8f1aba 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/http2_session.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_session.h
@@ -5,14 +5,15 @@
#include "absl/strings/string_view.h"
#include "http2/adapter/http2_protocol.h"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
-struct Http2SessionCallbacks {};
+struct QUICHE_EXPORT_PRIVATE Http2SessionCallbacks {};
// A class to represent the state of a single HTTP/2 connection.
-class Http2Session {
+class QUICHE_EXPORT_PRIVATE Http2Session {
public:
Http2Session() = default;
virtual ~Http2Session() {}
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_util.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_util.h
index 3ace28b2ce9..88e9a49e6e4 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/http2_util.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_util.h
@@ -2,13 +2,16 @@
#define QUICHE_HTTP2_ADAPTER_HTTP2_UTIL_H_
#include "http2/adapter/http2_protocol.h"
+#include "common/platform/api/quiche_export.h"
#include "spdy/core/spdy_protocol.h"
namespace http2 {
namespace adapter {
-spdy::SpdyErrorCode TranslateErrorCode(Http2ErrorCode code);
-Http2ErrorCode TranslateErrorCode(spdy::SpdyErrorCode code);
+QUICHE_EXPORT_PRIVATE spdy::SpdyErrorCode TranslateErrorCode(
+ Http2ErrorCode code);
+QUICHE_EXPORT_PRIVATE Http2ErrorCode
+TranslateErrorCode(spdy::SpdyErrorCode code);
} // namespace adapter
} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h
index 12729e75651..292021a23f4 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h
@@ -5,6 +5,7 @@
#include "absl/strings/string_view.h"
#include "http2/adapter/http2_protocol.h"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
@@ -44,20 +45,24 @@ namespace adapter {
// - OnCloseStream()
//
// More details are at RFC 7540 (go/http2spec).
-class Http2VisitorInterface {
+class QUICHE_EXPORT_PRIVATE Http2VisitorInterface {
public:
Http2VisitorInterface(const Http2VisitorInterface&) = delete;
Http2VisitorInterface& operator=(const Http2VisitorInterface&) = delete;
virtual ~Http2VisitorInterface() = default;
+ static const ssize_t kSendBlocked = 0;
+ static const ssize_t kSendError = -1;
+ // Called when there are serialized frames to send. Should return how many
+ // bytes were actually sent. May return kSendBlocked or kSendError.
+ virtual ssize_t OnReadyToSend(absl::string_view serialized) = 0;
+
// Called when a connection-level processing error has been encountered.
virtual void OnConnectionError() = 0;
// Called when the header for a frame is received.
- virtual void OnFrameHeader(Http2StreamId stream_id,
- size_t length,
- uint8_t type,
- uint8_t flags) {}
+ virtual void OnFrameHeader(Http2StreamId /*stream_id*/, size_t /*length*/,
+ uint8_t /*type*/, uint8_t /*flags*/) {}
// Called when a non-ack SETTINGS frame is received.
virtual void OnSettingsStart() = 0;
@@ -72,15 +77,28 @@ class Http2VisitorInterface {
virtual void OnSettingsAck() = 0;
// Called when the connection receives the header block for a HEADERS frame on
- // a stream but has not yet parsed individual headers.
- virtual void OnBeginHeadersForStream(Http2StreamId stream_id) = 0;
+ // a stream but has not yet parsed individual headers. Returns false if a
+ // fatal error has occurred.
+ virtual bool OnBeginHeadersForStream(Http2StreamId stream_id) = 0;
// Called when the connection receives the header |key| and |value| for a
// stream. The HTTP/2 pseudo-headers defined in RFC 7540 Sections 8.1.2.3 and
// 8.1.2.4 are also conveyed in this callback. This method is called after
- // OnBeginHeadersForStream().
- virtual void OnHeaderForStream(Http2StreamId stream_id, absl::string_view key,
- absl::string_view value) = 0;
+ // OnBeginHeadersForStream(). May return HEADER_RST_STREAM to indicate the
+ // header block should be rejected. This will cause the library to queue a
+ // RST_STREAM frame, which will have a default error code of INTERNAL_ERROR.
+ // The visitor implementation may choose to queue a RST_STREAM with a
+ // different error code instead, which should be done before returning
+ // HEADER_RST_STREAM. Returning HEADER_CONNECTION_ERROR will lead to a
+ // non-recoverable error on the connection.
+ enum OnHeaderResult {
+ HEADER_OK,
+ HEADER_CONNECTION_ERROR,
+ HEADER_RST_STREAM,
+ };
+ virtual OnHeaderResult OnHeaderForStream(Http2StreamId stream_id,
+ absl::string_view key,
+ absl::string_view value) = 0;
// Called when the connection has received the complete header block for a
// logical HEADERS frame on a stream (which may contain CONTINUATION frames,
@@ -133,6 +151,18 @@ class Http2VisitorInterface {
virtual void OnWindowUpdate(Http2StreamId stream_id,
int window_increment) = 0;
+ // Called immediately before a frame of the given type is sent. Should return
+ // 0 on success.
+ virtual int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+ size_t length, uint8_t flags) = 0;
+
+ // Called immediately after a frame of the given type is sent. Should return 0
+ // on success. |error_code| is only populated for RST_STREAM and GOAWAY frame
+ // types.
+ virtual int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+ size_t length, uint8_t flags,
+ uint32_t error_code) = 0;
+
// Called when the connection is ready to send data for a stream. The
// implementation should write at most |length| bytes of the data payload to
// the |destination_buffer| and set |end_stream| to true IFF there will be no
@@ -144,6 +174,12 @@ class Http2VisitorInterface {
ssize_t* written,
bool* end_stream) = 0;
+ // Called when the connection receives an invalid frame. |error_code| is a
+ // negative integer error code generated by the library. A return value of
+ // false will result in the connection entering an error state, with no
+ // further frame processing possible.
+ virtual bool OnInvalidFrame(Http2StreamId stream_id, int error_code) = 0;
+
// Called when the connection is ready to write metadata for |stream_id| to
// the wire. The implementation should write at most |length| bytes of the
// serialized metadata payload to the |buffer| and set |written| to the number
@@ -155,6 +191,7 @@ class Http2VisitorInterface {
// Called when the connection receives the beginning of a METADATA frame
// (which may itself be the middle of a logical metadata block). The metadata
// payload will be provided via subsequent calls to OnMetadataForStream().
+ // TODO(birenroy): Consider removing this unnecessary method.
virtual void OnBeginMetadataForStream(Http2StreamId stream_id,
size_t payload_length) = 0;
@@ -165,7 +202,11 @@ class Http2VisitorInterface {
// Called when the connection has finished receiving a logical metadata block
// for a stream. Note that there may be multiple metadata blocks for a stream.
- virtual void OnMetadataEndForStream(Http2StreamId stream_id) = 0;
+ // Returns false if there was an error unpacking the metadata payload.
+ virtual bool OnMetadataEndForStream(Http2StreamId stream_id) = 0;
+
+ // Invoked with an error message from the application.
+ virtual void OnErrorDebug(absl::string_view message) = 0;
protected:
Http2VisitorInterface() = default;
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h b/chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h
index 171d40f443f..3daa8053962 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h
@@ -2,6 +2,7 @@
#define QUICHE_HTTP2_ADAPTER_MOCK_HTTP2_VISITOR_INTERFACE_H_
#include "http2/adapter/http2_visitor_interface.h"
+#include "common/platform/api/quiche_export.h"
#include "common/platform/api/quiche_test.h"
namespace http2 {
@@ -9,10 +10,20 @@ namespace adapter {
namespace test {
// A mock visitor class, for use in tests.
-class MockHttp2Visitor : public Http2VisitorInterface {
+class QUICHE_NO_EXPORT MockHttp2Visitor : public Http2VisitorInterface {
public:
- MockHttp2Visitor() = default;
-
+ MockHttp2Visitor() {
+ ON_CALL(*this, OnBeginHeadersForStream)
+ .WillByDefault(testing::Return(true));
+ ON_CALL(*this, OnHeaderForStream).WillByDefault(testing::Return(HEADER_OK));
+ ON_CALL(*this, OnInvalidFrame).WillByDefault(testing::Return(true));
+ ON_CALL(*this, OnMetadataEndForStream).WillByDefault(testing::Return(true));
+ }
+
+ MOCK_METHOD(ssize_t,
+ OnReadyToSend,
+ (absl::string_view serialized),
+ (override));
MOCK_METHOD(void, OnConnectionError, (), (override));
MOCK_METHOD(
void,
@@ -23,15 +34,11 @@ class MockHttp2Visitor : public Http2VisitorInterface {
MOCK_METHOD(void, OnSetting, (Http2Setting setting), (override));
MOCK_METHOD(void, OnSettingsEnd, (), (override));
MOCK_METHOD(void, OnSettingsAck, (), (override));
- MOCK_METHOD(void,
- OnBeginHeadersForStream,
- (Http2StreamId stream_id),
+ MOCK_METHOD(bool, OnBeginHeadersForStream, (Http2StreamId stream_id),
(override));
- MOCK_METHOD(void,
- OnHeaderForStream,
- (Http2StreamId stream_id,
- absl::string_view key,
+ MOCK_METHOD(OnHeaderResult, OnHeaderForStream,
+ (Http2StreamId stream_id, absl::string_view key,
absl::string_view value),
(override));
@@ -89,6 +96,19 @@ class MockHttp2Visitor : public Http2VisitorInterface {
(Http2StreamId stream_id, int window_increment),
(override));
+ MOCK_METHOD(int, OnBeforeFrameSent,
+ (uint8_t frame_type, Http2StreamId stream_id, size_t length,
+ uint8_t flags),
+ (override));
+
+ MOCK_METHOD(int, OnFrameSent,
+ (uint8_t frame_type, Http2StreamId stream_id, size_t length,
+ uint8_t flags, uint32_t error_code),
+ (override));
+
+ MOCK_METHOD(bool, OnInvalidFrame, (Http2StreamId stream_id, int error_code),
+ (override));
+
MOCK_METHOD(void,
OnReadyToSendDataForStream,
(Http2StreamId stream_id,
@@ -114,10 +134,10 @@ class MockHttp2Visitor : public Http2VisitorInterface {
(Http2StreamId stream_id, absl::string_view metadata),
(override));
- MOCK_METHOD(void,
- OnMetadataEndForStream,
- (Http2StreamId stream_id),
+ MOCK_METHOD(bool, OnMetadataEndForStream, (Http2StreamId stream_id),
(override));
+
+ MOCK_METHOD(void, OnErrorDebug, (absl::string_view message), (override));
};
} // namespace test
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc b/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc
index 4699a371add..4243347e758 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc
@@ -109,12 +109,19 @@ nghttp2_session_callbacks_unique_ptr MockNghttp2Callbacks::GetCallbacks() {
nghttp2_session_callbacks_set_error_callback2(
callbacks,
- [](nghttp2_session* session, int lib_error_code, const char* msg,
+ [](nghttp2_session* /*session*/, int lib_error_code, const char* msg,
size_t len, void* user_data) -> int {
return static_cast<MockNghttp2Callbacks*>(user_data)->OnErrorCallback2(
lib_error_code, msg, len);
});
+ nghttp2_session_callbacks_set_pack_extension_callback(
+ callbacks,
+ [](nghttp2_session*, uint8_t* buf, size_t len, const nghttp2_frame* frame,
+ void* user_data) -> ssize_t {
+ return static_cast<MockNghttp2Callbacks*>(user_data)->OnPackExtension(
+ buf, len, frame);
+ });
return MakeCallbacksPtr(callbacks);
}
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h b/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h
index e2794575c8d..08d15be7d7d 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h
@@ -4,6 +4,7 @@
#include "absl/strings/string_view.h"
#include "http2/adapter/nghttp2_util.h"
#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+#include "common/platform/api/quiche_export.h"
#include "common/platform/api/quiche_test.h"
namespace http2 {
@@ -12,7 +13,7 @@ namespace test {
// This class provides a set of mock nghttp2 callbacks for use in unit test
// expectations.
-class MockNghttp2Callbacks {
+class QUICHE_NO_EXPORT MockNghttp2Callbacks {
public:
MockNghttp2Callbacks() = default;
@@ -71,6 +72,9 @@ class MockNghttp2Callbacks {
OnErrorCallback2,
(int lib_error_code, const char* msg, size_t len),
());
+
+ MOCK_METHOD(ssize_t, OnPackExtension,
+ (uint8_t * buf, size_t len, const nghttp2_frame* frame), ());
};
} // namespace test
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc
index 878c040ef72..9b444a3243d 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc
@@ -4,6 +4,7 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "http2/adapter/nghttp2_callbacks.h"
+#include "http2/adapter/nghttp2_data_provider.h"
#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
#include "common/platform/api/quiche_logging.h"
#include "common/quiche_endian.h"
@@ -13,20 +14,26 @@ namespace adapter {
/* static */
std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateClientAdapter(
- Http2VisitorInterface& visitor) {
- auto adapter = new NgHttp2Adapter(visitor, Perspective::kClient);
+ Http2VisitorInterface& visitor, const nghttp2_option* options) {
+ auto adapter = new NgHttp2Adapter(visitor, Perspective::kClient, options);
adapter->Initialize();
return absl::WrapUnique(adapter);
}
/* static */
std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateServerAdapter(
- Http2VisitorInterface& visitor) {
- auto adapter = new NgHttp2Adapter(visitor, Perspective::kServer);
+ Http2VisitorInterface& visitor, const nghttp2_option* options) {
+ auto adapter = new NgHttp2Adapter(visitor, Perspective::kServer, options);
adapter->Initialize();
return absl::WrapUnique(adapter);
}
+bool NgHttp2Adapter::IsServerSession() const {
+ int result = nghttp2_session_check_server_session(session_->raw_ptr());
+ QUICHE_DCHECK_EQ(perspective_ == Perspective::kServer, result > 0);
+ return result > 0;
+}
+
ssize_t NgHttp2Adapter::ProcessBytes(absl::string_view bytes) {
const ssize_t processed_bytes = session_->ProcessBytes(bytes);
if (processed_bytes < 0) {
@@ -64,6 +71,10 @@ void NgHttp2Adapter::SubmitPing(Http2PingId ping_id) {
nghttp2_submit_ping(session_->raw_ptr(), NGHTTP2_FLAG_NONE, opaque_data);
}
+void NgHttp2Adapter::SubmitShutdownNotice() {
+ nghttp2_submit_shutdown_notice(session_->raw_ptr());
+}
+
void NgHttp2Adapter::SubmitGoAway(Http2StreamId last_accepted_stream_id,
Http2ErrorCode error_code,
absl::string_view opaque_data) {
@@ -79,32 +90,55 @@ void NgHttp2Adapter::SubmitWindowUpdate(Http2StreamId stream_id,
stream_id, window_increment);
}
-void NgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id,
- bool end_metadata) {
+void NgHttp2Adapter::SubmitMetadata(
+ Http2StreamId /*stream_id*/, std::unique_ptr<MetadataSource> /*source*/) {
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);
+int NgHttp2Adapter::Send() {
+ const int result = nghttp2_session_send(session_->raw_ptr());
+ if (result != 0) {
+ QUICHE_VLOG(1) << "nghttp2_session_send returned " << result;
+ visitor_.OnConnectionError();
+ }
return result;
}
-int NgHttp2Adapter::GetPeerConnectionWindow() const {
+int NgHttp2Adapter::GetSendWindowSize() const {
return session_->GetRemoteWindowSize();
}
+int NgHttp2Adapter::GetStreamSendWindowSize(Http2StreamId stream_id) const {
+ return nghttp2_session_get_stream_remote_window_size(session_->raw_ptr(),
+ stream_id);
+}
+
+int NgHttp2Adapter::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const {
+ return nghttp2_session_get_stream_effective_local_window_size(
+ session_->raw_ptr(), stream_id);
+}
+
+int NgHttp2Adapter::GetStreamReceiveWindowSize(Http2StreamId stream_id) const {
+ return nghttp2_session_get_stream_local_window_size(session_->raw_ptr(),
+ stream_id);
+}
+
+int NgHttp2Adapter::GetReceiveWindowSize() const {
+ return nghttp2_session_get_local_window_size(session_->raw_ptr());
+}
+
+int NgHttp2Adapter::GetHpackEncoderDynamicTableSize() const {
+ return nghttp2_session_get_hd_deflate_dynamic_table_size(session_->raw_ptr());
+}
+
+int NgHttp2Adapter::GetHpackDecoderDynamicTableSize() const {
+ return nghttp2_session_get_hd_inflate_dynamic_table_size(session_->raw_ptr());
+}
+
+Http2StreamId NgHttp2Adapter::GetHighestReceivedStreamId() const {
+ return nghttp2_session_get_last_proc_stream_id(session_->raw_ptr());
+}
+
void NgHttp2Adapter::MarkDataConsumedForStream(Http2StreamId stream_id,
size_t num_bytes) {
int rc = session_->Consume(stream_id, num_bytes);
@@ -125,24 +159,96 @@ void NgHttp2Adapter::SubmitRst(Http2StreamId stream_id,
}
}
+int32_t NgHttp2Adapter::SubmitRequest(
+ absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source, void* stream_user_data) {
+ auto nvs = GetNghttp2Nvs(headers);
+ std::unique_ptr<nghttp2_data_provider> provider =
+ MakeDataProvider(data_source.get());
+
+ int32_t stream_id =
+ nghttp2_submit_request(session_->raw_ptr(), nullptr, nvs.data(),
+ nvs.size(), provider.get(), stream_user_data);
+ // TODO(birenroy): clean up data source on stream close
+ sources_.emplace(stream_id, std::move(data_source));
+ QUICHE_VLOG(1) << "Submitted request with " << nvs.size()
+ << " request headers and user data " << stream_user_data
+ << "; resulted in stream " << stream_id;
+ return stream_id;
+}
+
+int NgHttp2Adapter::SubmitResponse(
+ Http2StreamId stream_id, absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source) {
+ auto nvs = GetNghttp2Nvs(headers);
+ std::unique_ptr<nghttp2_data_provider> provider =
+ MakeDataProvider(data_source.get());
+
+ // TODO(birenroy): clean up data source on stream close
+ sources_.emplace(stream_id, std::move(data_source));
+
+ int result = nghttp2_submit_response(session_->raw_ptr(), stream_id,
+ nvs.data(), nvs.size(), provider.get());
+ QUICHE_VLOG(1) << "Submitted response with " << nvs.size()
+ << " response headers; result = " << result;
+ return result;
+}
+
+int NgHttp2Adapter::SubmitTrailer(Http2StreamId stream_id,
+ absl::Span<const Header> trailers) {
+ auto nvs = GetNghttp2Nvs(trailers);
+ int result = nghttp2_submit_trailer(session_->raw_ptr(), stream_id,
+ nvs.data(), nvs.size());
+ QUICHE_VLOG(1) << "Submitted trailers with " << nvs.size()
+ << " response trailers; result = " << result;
+ return result;
+}
+
+void NgHttp2Adapter::SetStreamUserData(Http2StreamId stream_id,
+ void* stream_user_data) {
+ nghttp2_session_set_stream_user_data(session_->raw_ptr(), stream_id,
+ stream_user_data);
+}
+
+void* NgHttp2Adapter::GetStreamUserData(Http2StreamId stream_id) {
+ return nghttp2_session_get_stream_user_data(session_->raw_ptr(), stream_id);
+}
+
+bool NgHttp2Adapter::ResumeStream(Http2StreamId stream_id) {
+ return 0 == nghttp2_session_resume_data(session_->raw_ptr(), stream_id);
+}
+
NgHttp2Adapter::NgHttp2Adapter(Http2VisitorInterface& visitor,
- Perspective perspective)
- : Http2Adapter(visitor), visitor_(visitor), perspective_(perspective) {}
+ Perspective perspective,
+ const nghttp2_option* options)
+ : Http2Adapter(visitor),
+ visitor_(visitor),
+ options_(options),
+ 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_));
+ nghttp2_option* owned_options = nullptr;
+ if (options_ == nullptr) {
+ nghttp2_option_new(&owned_options);
+ // Set some common options for compatibility.
+ nghttp2_option_set_no_closed_streams(owned_options, 1);
+ nghttp2_option_set_no_auto_window_update(owned_options, 1);
+ nghttp2_option_set_max_send_header_block_length(owned_options, 0x2000000);
+ nghttp2_option_set_max_outbound_ack(owned_options, 10000);
+ nghttp2_option_set_user_recv_extension_type(owned_options,
+ kMetadataFrameType);
+ options_ = owned_options;
+ }
+
+ session_ = absl::make_unique<NgHttp2Session>(perspective_,
+ callbacks::Create(), options_,
+ static_cast<void*>(&visitor_));
+ if (owned_options != nullptr) {
+ nghttp2_option_del(owned_options);
+ }
+ options_ = nullptr;
}
} // namespace adapter
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h
index 13c2ffcc3f4..151f9a7ae5a 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h
@@ -1,35 +1,34 @@
#ifndef QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_
#define QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_
+#include "absl/container/flat_hash_map.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"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
-class NgHttp2Adapter : public Http2Adapter {
+class QUICHE_EXPORT_PRIVATE NgHttp2Adapter : public Http2Adapter {
public:
~NgHttp2Adapter() override;
- // Creates an adapter that functions as a client.
+ // Creates an adapter that functions as a client. Does not take ownership of
+ // |options|.
static std::unique_ptr<NgHttp2Adapter> CreateClientAdapter(
- Http2VisitorInterface& visitor);
+ Http2VisitorInterface& visitor, const nghttp2_option* options = nullptr);
- // Creates an adapter that functions as a server.
+ // Creates an adapter that functions as a server. Does not take ownership of
+ // |options|.
static std::unique_ptr<NgHttp2Adapter> CreateServerAdapter(
- Http2VisitorInterface& visitor);
+ Http2VisitorInterface& visitor, const nghttp2_option* options = nullptr);
- // Processes the incoming |bytes| as HTTP/2 and invokes callbacks on the
- // |visitor_| as appropriate.
- ssize_t ProcessBytes(absl::string_view bytes) override;
+ bool IsServerSession() const override;
- // Submits the |settings| to be written to the peer, e.g., as part of the
- // HTTP/2 connection preface.
+ ssize_t ProcessBytes(absl::string_view bytes) override;
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,
@@ -40,47 +39,58 @@ class NgHttp2Adapter : public Http2Adapter {
// 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 SubmitShutdownNotice() override;
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;
+ void SubmitRst(Http2StreamId stream_id, Http2ErrorCode error_code) override;
+
+ void SubmitMetadata(Http2StreamId stream_id,
+ std::unique_ptr<MetadataSource> source) override;
+
+ int Send() override;
+
+ int GetSendWindowSize() const override;
+ int GetStreamSendWindowSize(Http2StreamId stream_id) const 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;
+ int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const override;
+ int GetStreamReceiveWindowSize(Http2StreamId stream_id) const override;
+ int GetReceiveWindowSize() const override;
- // Returns the connection-level flow control window for the peer.
- int GetPeerConnectionWindow() const override;
+ int GetHpackEncoderDynamicTableSize() const override;
+ int GetHpackDecoderDynamicTableSize() const override;
+
+ Http2StreamId GetHighestReceivedStreamId() 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;
+ int32_t SubmitRequest(absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source,
+ void* user_data) override;
+
+ int SubmitResponse(Http2StreamId stream_id, absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source) override;
+
+ int SubmitTrailer(Http2StreamId stream_id,
+ absl::Span<const Header> trailers) override;
+
+ void SetStreamUserData(Http2StreamId stream_id, void* user_data) override;
+ void* GetStreamUserData(Http2StreamId stream_id) override;
+
+ bool ResumeStream(Http2StreamId stream_id) 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);
+ NgHttp2Adapter(Http2VisitorInterface& visitor, Perspective perspective,
+ const nghttp2_option* options);
// Performs any necessary initialization of the underlying HTTP/2 session,
// such as preparing initial SETTINGS.
@@ -88,7 +98,10 @@ class NgHttp2Adapter : public Http2Adapter {
std::unique_ptr<NgHttp2Session> session_;
Http2VisitorInterface& visitor_;
+ const nghttp2_option* options_;
Perspective perspective_;
+
+ absl::flat_hash_map<int32_t, std::unique_ptr<DataFrameSource>> sources_;
};
} // namespace adapter
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc
index 3aec115dbeb..785e476c94d 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc
@@ -1,6 +1,7 @@
#include "http2/adapter/nghttp2_adapter.h"
#include "http2/adapter/mock_http2_visitor.h"
+#include "http2/adapter/nghttp2_test_utils.h"
#include "http2/adapter/test_frame_sequence.h"
#include "http2/adapter/test_utils.h"
#include "common/platform/api/quiche_test.h"
@@ -22,21 +23,46 @@ enum FrameType {
PING,
GOAWAY,
WINDOW_UPDATE,
+ CONTINUATION,
};
+// This send callback assumes |source|'s pointer is a TestDataSource, and
+// |user_data| is a Http2VisitorInterface.
+int TestSendCallback(nghttp2_session*, nghttp2_frame* /*frame*/,
+ const uint8_t* framehd, size_t length,
+ nghttp2_data_source* source, void* user_data) {
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ // Send the frame header via the visitor.
+ ssize_t result = visitor->OnReadyToSend(ToStringView(framehd, 9));
+ if (result == 0) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+ auto* test_source = static_cast<TestDataSource*>(source->ptr);
+ absl::string_view payload = test_source->ReadNext(length);
+ // Send the frame payload via the visitor.
+ visitor->OnReadyToSend(payload);
+ return 0;
+}
+
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());
+ EXPECT_FALSE(adapter->IsServerSession());
}
TEST(NgHttp2AdapterTest, ClientHandlesFrames) {
- testing::StrictMock<MockHttp2Visitor> visitor;
+ DataSavingVisitor visitor;
auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
- std::string serialized = adapter->GetBytesToWrite(absl::nullopt);
- EXPECT_THAT(serialized, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(),
+ testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+ visitor.Clear();
+
+ EXPECT_EQ(0, adapter->GetHighestReceivedStreamId());
const std::string initial_frames = TestFrameSequence()
.ServerPreface()
@@ -58,56 +84,95 @@ TEST(NgHttp2AdapterTest, ClientHandlesFrames) {
const ssize_t initial_result = adapter->ProcessBytes(initial_frames);
EXPECT_EQ(initial_frames.size(), initial_result);
- EXPECT_EQ(adapter->GetPeerConnectionWindow(),
- kDefaultInitialStreamWindowSize + 1000);
+ EXPECT_EQ(adapter->GetSendWindowSize(), kInitialFlowControlWindowSize + 1000);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0));
+
+ result = adapter->Send();
// Some bytes should have been serialized.
- serialized = adapter->GetBytesToWrite(absl::nullopt);
- EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::PING}));
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::PING}));
+ visitor.Clear();
const std::vector<const Header> headers1 =
ToHeaders({{":method", "GET"},
{":scheme", "http"},
{":authority", "example.com"},
{":path", "/this/is/request/one"}});
- const auto nvs1 = GetNghttp2Nvs(headers1);
const std::vector<const Header> headers2 =
ToHeaders({{":method", "GET"},
{":scheme", "http"},
{":authority", "example.com"},
{":path", "/this/is/request/two"}});
- const auto nvs2 = GetNghttp2Nvs(headers2);
const std::vector<const Header> headers3 =
ToHeaders({{":method", "GET"},
{":scheme", "http"},
{":authority", "example.com"},
{":path", "/this/is/request/three"}});
- const auto nvs3 = GetNghttp2Nvs(headers3);
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const char* kSentinel3 = "arbitrary pointer 3";
const int32_t stream_id1 =
- nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs1.data(),
- nvs1.size(), nullptr, nullptr);
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
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);
+ const int32_t stream_id2 = adapter->SubmitRequest(headers2, 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);
+ adapter->SubmitRequest(headers3, nullptr, const_cast<char*>(kSentinel3));
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 char* kSentinel2 = "arbitrary pointer 2";
+ adapter->SetStreamUserData(stream_id2, const_cast<char*>(kSentinel2));
+ adapter->SetStreamUserData(stream_id3, nullptr);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id2, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id2, _, 0x5, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id3, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id3, _, 0x5, 0));
+
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ // All streams are active and have not yet received any data, so the receive
+ // window should be at the initial value.
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowSize(stream_id1));
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowSize(stream_id2));
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowSize(stream_id3));
+
+ // Upper bound on the flow control receive window should be the initial value.
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowLimit(stream_id1));
+
+ // Connection has not yet received any data.
+ EXPECT_EQ(kInitialFlowControlWindowSize, adapter->GetReceiveWindowSize());
+
+ EXPECT_EQ(0, adapter->GetHighestReceivedStreamId());
+
+ EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(stream_id1));
+ EXPECT_EQ(kSentinel2, adapter->GetStreamUserData(stream_id2));
+ EXPECT_EQ(nullptr, adapter->GetStreamUserData(stream_id3));
+
+ EXPECT_EQ(0, adapter->GetHpackDecoderDynamicTableSize());
const std::string stream_frames =
TestFrameSequence()
@@ -140,6 +205,29 @@ TEST(NgHttp2AdapterTest, ClientHandlesFrames) {
const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
EXPECT_EQ(stream_frames.size(), stream_result);
+ // First stream has received some data.
+ EXPECT_GT(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowSize(stream_id1));
+ // Second stream was closed.
+ EXPECT_EQ(-1, adapter->GetStreamReceiveWindowSize(stream_id2));
+ // Third stream has not received any data.
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowSize(stream_id3));
+
+ // Connection window should be the same as the first stream.
+ EXPECT_EQ(adapter->GetReceiveWindowSize(),
+ adapter->GetStreamReceiveWindowSize(stream_id1));
+
+ // Upper bound on the flow control receive window should still be the initial
+ // value.
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowLimit(stream_id1));
+
+ EXPECT_GT(adapter->GetHpackDecoderDynamicTableSize(), 0);
+
+ // Should be 3, but this method only works for server adapters.
+ EXPECT_EQ(0, adapter->GetHighestReceivedStreamId());
+
// Even though the client recieved a GOAWAY, streams 1 and 5 are still active.
EXPECT_TRUE(adapter->session().want_read());
@@ -154,14 +242,801 @@ TEST(NgHttp2AdapterTest, ClientHandlesFrames) {
.Data(1, "", true)
.RstStream(5, Http2ErrorCode::REFUSED_STREAM)
.Serialize());
+
+ // Should be 5, but this method only works for server adapters.
+ EXPECT_EQ(0, adapter->GetHighestReceivedStreamId());
+
// 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());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), testing::IsEmpty());
+}
+
+TEST(NgHttp2AdapterTest, ClientHandlesTrailers) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Headers(1, {{"final-status", "A-OK"}},
+ /*fin=*/true)
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ 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(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, "final-status", "A-OK"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnEndStream(1));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(NgHttp2AdapterTest, ClientHandlesMetadata) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .Metadata(0, "Example connection metadata")
+ .Headers(1,
+ {{":status", "200"},
+ {"server", "my-fake-server"},
+ {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}},
+ /*fin=*/false)
+ .Metadata(1, "Example stream metadata")
+ .Data(1, "This is the response body.", true)
+ .Serialize();
+
+ // 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, _, kMetadataFrameType, 4));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataEndForStream(0));
+ 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, _, kMetadataFrameType, 4));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataEndForStream(1));
+ EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 1));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, 26));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body."));
+ EXPECT_CALL(visitor, OnEndStream(1));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(NgHttp2AdapterTest, ClientHandlesInvalidTrailers) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Headers(1, {{":bad-status", "9000"}},
+ /*fin=*/true)
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ 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(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(
+ visitor,
+ OnErrorDebug("Invalid HTTP header field was received: frame type: 1, "
+ "stream: 1, name: [:bad-status], value: [9000]"));
+ EXPECT_CALL(visitor, OnInvalidFrame(1, -531));
+
+ // Bad status trailer will cause a PROTOCOL_ERROR. The header is never
+ // delivered in an OnHeaderForStream callback.
+
+ const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, 1));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM}));
+}
+
+TEST(NgHttp2AdapterTest, ClientRstStreamWhileHandlingHeaders) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ 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"))
+ .WillOnce(testing::DoAll(
+ testing::InvokeWithoutArgs([&adapter]() {
+ adapter->SubmitRst(1, Http2ErrorCode::REFUSED_STREAM);
+ }),
+ testing::Return(Http2VisitorInterface::HEADER_RST_STREAM)));
+
+ const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, stream_id1, 4, 0x0,
+ static_cast<int>(Http2ErrorCode::REFUSED_STREAM)));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::REFUSED_STREAM));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM}));
+}
+
+TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeaders) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ 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"))
+ .WillOnce(
+ testing::Return(Http2VisitorInterface::HEADER_CONNECTION_ERROR));
+ EXPECT_CALL(visitor, OnConnectionError());
+
+ const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(-902 /* NGHTTP2_ERR_CALLBACK_FAILURE */, stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(NgHttp2AdapterTest, ClientRejectsHeaders) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1))
+ .WillOnce(testing::Return(false));
+ // Rejecting headers leads to a connection error.
+ EXPECT_CALL(visitor, OnConnectionError());
+
+ const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(NgHttp2AdapterTest, ClientSubmitRequest) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ // Client preface does not appear to include the mandatory SETTINGS frame.
+ EXPECT_THAT(visitor.data(),
+ testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+ visitor.Clear();
+
+ const std::string initial_frames =
+ TestFrameSequence().ServerPreface().Serialize();
+ testing::InSequence s;
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ const ssize_t initial_result = adapter->ProcessBytes(initial_frames);
+ EXPECT_EQ(initial_frames.size(), initial_result);
+
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ EXPECT_EQ(0, adapter->GetHpackEncoderDynamicTableSize());
+ EXPECT_FALSE(adapter->session().want_write());
+ const char* kSentinel = "";
+ const absl::string_view kBody = "This is an example request body.";
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true);
+ body1->AppendPayload(kBody);
+ body1->EndData();
+ int stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(body1), const_cast<char*>(kSentinel));
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowSize(stream_id));
+ EXPECT_EQ(kInitialFlowControlWindowSize, adapter->GetReceiveWindowSize());
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowLimit(stream_id));
+
+ EXPECT_GT(adapter->GetHpackEncoderDynamicTableSize(), 0);
+
+ // Some data was sent, so the remaining send window size should be less than
+ // the default.
+ EXPECT_LT(adapter->GetStreamSendWindowSize(stream_id),
+ kInitialFlowControlWindowSize);
+ EXPECT_GT(adapter->GetStreamSendWindowSize(stream_id), 0);
+ // Send window for a nonexistent stream is not available.
+ EXPECT_EQ(-1, adapter->GetStreamSendWindowSize(stream_id + 2));
+
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody));
+ visitor.Clear();
+ EXPECT_FALSE(adapter->session().want_write());
+
+ stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ nullptr, nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+ const char* kSentinel2 = "arbitrary pointer 2";
+ EXPECT_EQ(nullptr, adapter->GetStreamUserData(stream_id));
+ adapter->SetStreamUserData(stream_id, const_cast<char*>(kSentinel2));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+
+ EXPECT_EQ(kSentinel2, adapter->GetStreamUserData(stream_id));
+
+ // No data was sent (just HEADERS), so the remaining send window size should
+ // still be the default.
+ EXPECT_EQ(adapter->GetStreamSendWindowSize(stream_id),
+ kInitialFlowControlWindowSize);
+}
+
+// This is really a test of the MakeZeroCopyDataFrameSource adapter, but I
+// wasn't sure where else to put it.
+TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProvider) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ // Client preface does not appear to include the mandatory SETTINGS frame.
+ EXPECT_THAT(visitor.data(),
+ testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+ visitor.Clear();
+
+ const std::string initial_frames =
+ TestFrameSequence().ServerPreface().Serialize();
+ testing::InSequence s;
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ const ssize_t initial_result = adapter->ProcessBytes(initial_frames);
+ EXPECT_EQ(initial_frames.size(), initial_result);
+
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ EXPECT_FALSE(adapter->session().want_write());
+ const absl::string_view kBody = "This is an example request body.";
+ // This test will use TestDataSource as the source of the body payload data.
+ TestDataSource body1{kBody};
+ // The TestDataSource is wrapped in the nghttp2_data_provider data type.
+ nghttp2_data_provider provider = body1.MakeDataProvider();
+ nghttp2_send_data_callback send_callback = &TestSendCallback;
+
+ // This call transforms it back into a DataFrameSource, which is compatible
+ // with the Http2Adapter API.
+ std::unique_ptr<DataFrameSource> frame_source =
+ MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback));
+ int stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(frame_source), nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody));
+ EXPECT_FALSE(adapter->session().want_write());
+}
+
+// This test verifies how nghttp2 behaves when a data source becomes
+// read-blocked.
+TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndReadBlock) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ const absl::string_view kBody = "This is an example request body.";
+ // This test will use TestDataSource as the source of the body payload data.
+ TestDataSource body1{kBody};
+ body1.set_is_data_available(false);
+ // The TestDataSource is wrapped in the nghttp2_data_provider data type.
+ nghttp2_data_provider provider = body1.MakeDataProvider();
+ nghttp2_send_data_callback send_callback = &TestSendCallback;
+
+ // This call transforms it back into a DataFrameSource, which is compatible
+ // with the Http2Adapter API.
+ std::unique_ptr<DataFrameSource> frame_source =
+ MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback));
+ int stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(frame_source), nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ // Client preface does not appear to include the mandatory SETTINGS frame.
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+ EXPECT_FALSE(adapter->session().want_write());
+
+ // Resume the deferred stream.
+ body1.set_is_data_available(true);
+ EXPECT_TRUE(adapter->ResumeStream(stream_id));
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA}));
+ EXPECT_FALSE(adapter->session().want_write());
+
+ // Stream data is done, so this stream cannot be resumed.
+ EXPECT_FALSE(adapter->ResumeStream(stream_id));
+ EXPECT_FALSE(adapter->session().want_write());
+}
+
+// This test verifies how nghttp2 behaves when a data source is read block, then
+// ends with an empty DATA frame.
+TEST(NgHttp2AdapterTest, ClientSubmitRequestEmptyDataWithFin) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ const absl::string_view kEmptyBody = "";
+ // This test will use TestDataSource as the source of the body payload data.
+ TestDataSource body1{kEmptyBody};
+ body1.set_is_data_available(false);
+ // The TestDataSource is wrapped in the nghttp2_data_provider data type.
+ nghttp2_data_provider provider = body1.MakeDataProvider();
+ nghttp2_send_data_callback send_callback = &TestSendCallback;
+
+ // This call transforms it back into a DataFrameSource, which is compatible
+ // with the Http2Adapter API.
+ std::unique_ptr<DataFrameSource> frame_source =
+ MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback));
+ int stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(frame_source), nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ // Client preface does not appear to include the mandatory SETTINGS frame.
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+ EXPECT_FALSE(adapter->session().want_write());
+
+ // Resume the deferred stream.
+ body1.set_is_data_available(true);
+ EXPECT_TRUE(adapter->ResumeStream(stream_id));
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 0, 0x1, 0));
+
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA}));
+ EXPECT_FALSE(adapter->session().want_write());
+
+ // Stream data is done, so this stream cannot be resumed.
+ EXPECT_FALSE(adapter->ResumeStream(stream_id));
+ EXPECT_FALSE(adapter->session().want_write());
+}
+
+// This test verifies how nghttp2 behaves when a connection becomes
+// write-blocked.
+TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndWriteBlock) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+ const absl::string_view kBody = "This is an example request body.";
+ // This test will use TestDataSource as the source of the body payload data.
+ TestDataSource body1{kBody};
+ // The TestDataSource is wrapped in the nghttp2_data_provider data type.
+ nghttp2_data_provider provider = body1.MakeDataProvider();
+ nghttp2_send_data_callback send_callback = &TestSendCallback;
+
+ // This call transforms it back into a DataFrameSource, which is compatible
+ // with the Http2Adapter API.
+ std::unique_ptr<DataFrameSource> frame_source =
+ MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback));
+ int stream_id =
+ adapter->SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(frame_source), nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ visitor.set_is_write_blocked(true);
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), testing::IsEmpty());
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
+ visitor.set_is_write_blocked(false);
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+
+ // Client preface does not appear to include the mandatory SETTINGS frame.
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ EXPECT_FALSE(adapter->session().want_write());
}
TEST(NgHttp2AdapterTest, ServerConstruction) {
@@ -170,12 +1045,16 @@ TEST(NgHttp2AdapterTest, ServerConstruction) {
ASSERT_NE(nullptr, adapter);
EXPECT_TRUE(adapter->session().want_read());
EXPECT_FALSE(adapter->session().want_write());
+ EXPECT_TRUE(adapter->IsServerSession());
}
TEST(NgHttp2AdapterTest, ServerHandlesFrames) {
- testing::StrictMock<MockHttp2Visitor> visitor;
+ DataSavingVisitor visitor;
auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ EXPECT_EQ(0, adapter->GetHighestReceivedStreamId());
+ EXPECT_EQ(0, adapter->GetHpackDecoderDynamicTableSize());
+
const std::string frames = TestFrameSequence()
.ClientPreface()
.Ping(42)
@@ -199,6 +1078,8 @@ TEST(NgHttp2AdapterTest, ServerHandlesFrames) {
.Serialize();
testing::InSequence s;
+ const char* kSentinel1 = "arbitrary pointer 1";
+
// Client preface (empty SETTINGS)
EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
EXPECT_CALL(visitor, OnSettingsStart());
@@ -214,7 +1095,10 @@ TEST(NgHttp2AdapterTest, ServerHandlesFrames) {
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, OnEndHeadersForStream(1))
+ .WillOnce(testing::InvokeWithoutArgs([&adapter, kSentinel1]() {
+ adapter->SetStreamUserData(1, const_cast<char*>(kSentinel1));
+ }));
EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0));
EXPECT_CALL(visitor, OnWindowUpdate(1, 2000));
EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0));
@@ -237,16 +1121,506 @@ TEST(NgHttp2AdapterTest, ServerHandlesFrames) {
const ssize_t result = adapter->ProcessBytes(frames);
EXPECT_EQ(frames.size(), result);
- EXPECT_EQ(adapter->GetPeerConnectionWindow(),
- kDefaultInitialStreamWindowSize + 1000);
+ EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(1));
+
+ EXPECT_GT(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowSize(1));
+ EXPECT_EQ(adapter->GetStreamReceiveWindowSize(1),
+ adapter->GetReceiveWindowSize());
+ // Upper bound should still be the original value.
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ adapter->GetStreamReceiveWindowLimit(1));
+
+ EXPECT_GT(adapter->GetHpackDecoderDynamicTableSize(), 0);
+
+ // Because stream 3 has already been closed, it's not possible to set user
+ // data.
+ const char* kSentinel3 = "another arbitrary pointer";
+ adapter->SetStreamUserData(3, const_cast<char*>(kSentinel3));
+ EXPECT_EQ(nullptr, adapter->GetStreamUserData(3));
+
+ EXPECT_EQ(3, adapter->GetHighestReceivedStreamId());
+
+ EXPECT_EQ(adapter->GetSendWindowSize(), kInitialFlowControlWindowSize + 1000);
EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0));
+
+ int send_result = adapter->Send();
// Some bytes should have been serialized.
- std::string serialized = adapter->GetBytesToWrite(absl::nullopt);
+ EXPECT_EQ(0, send_result);
// SETTINGS ack, two PING acks.
- EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::PING,
- spdy::SpdyFrameType::PING}));
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::PING,
+ spdy::SpdyFrameType::PING}));
+}
+
+TEST(NgHttp2AdapterTest, ServerErrorWhileHandlingHeaders) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"},
+ {"accept", "some bogus value!"}},
+ /*fin=*/false)
+ .WindowUpdate(1, 2000)
+ .Data(1, "This is the request body.")
+ .WindowUpdate(0, 2000)
+ .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(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, OnHeaderForStream(1, "accept", "some bogus value!"))
+ .WillOnce(testing::Return(Http2VisitorInterface::HEADER_RST_STREAM));
+ EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0));
+ EXPECT_CALL(visitor, OnWindowUpdate(1, 2000));
+ // DATA frame is not delivered to the visitor.
+ EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0));
+ EXPECT_CALL(visitor, OnWindowUpdate(0, 2000));
+
+ const ssize_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, 1, 4, 0x0,
+ static_cast<int>(Http2ErrorCode::INTERNAL_ERROR)));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::INTERNAL_ERROR));
+
+ int send_result = adapter->Send();
+ // Some bytes should have been serialized.
+ EXPECT_EQ(0, send_result);
+ // SETTINGS ack
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM}));
+}
+
+TEST(NgHttp2AdapterTest, ServerSubmitResponse) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ EXPECT_FALSE(adapter->session().want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ 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))
+ .WillOnce(testing::InvokeWithoutArgs([&adapter, kSentinel1]() {
+ adapter->SetStreamUserData(1, const_cast<char*>(kSentinel1));
+ }));
+ EXPECT_CALL(visitor, OnEndStream(1));
+
+ const ssize_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ EXPECT_EQ(1, adapter->GetHighestReceivedStreamId());
+
+ // Server will want to send a SETTINGS ack.
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ EXPECT_EQ(0, adapter->GetHpackEncoderDynamicTableSize());
+
+ EXPECT_FALSE(adapter->session().want_write());
+ const absl::string_view kBody = "This is an example response body.";
+ // A data fin is not sent so that the stream remains open, and the flow
+ // control state can be verified.
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false);
+ body1->AppendPayload(kBody);
+ int submit_result = adapter->SubmitResponse(
+ 1,
+ ToHeaders({{":status", "404"},
+ {"x-comment", "I have no idea what you're talking about."}}),
+ std::move(body1));
+ EXPECT_EQ(submit_result, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ // Stream user data should have been set successfully after receiving headers.
+ EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(1));
+ adapter->SetStreamUserData(1, nullptr);
+ EXPECT_EQ(nullptr, adapter->GetStreamUserData(1));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody));
+ EXPECT_FALSE(adapter->session().want_write());
+
+ // Some data was sent, so the remaining send window size should be less than
+ // the default.
+ EXPECT_LT(adapter->GetStreamSendWindowSize(1), kInitialFlowControlWindowSize);
+ EXPECT_GT(adapter->GetStreamSendWindowSize(1), 0);
+ // Send window for a nonexistent stream is not available.
+ EXPECT_EQ(adapter->GetStreamSendWindowSize(3), -1);
+
+ EXPECT_GT(adapter->GetHpackEncoderDynamicTableSize(), 0);
+}
+
+// Should also test: client attempts shutdown, server attempts shutdown after an
+// explicit GOAWAY.
+TEST(NgHttp2AdapterTest, ServerSendsShutdown) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/false)
+ .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(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));
+
+ const ssize_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ adapter->SubmitShutdownNotice();
+
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::GOAWAY}));
+}
+
+TEST(NgHttp2AdapterTest, ServerSendsTrailers) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ EXPECT_FALSE(adapter->session().want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ 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, OnEndStream(1));
+
+ const ssize_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ // Server will want to send a SETTINGS ack.
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ EXPECT_FALSE(adapter->session().want_write());
+ const absl::string_view kBody = "This is an example response body.";
+
+ // The body source must indicate that the end of the body is not the end of
+ // the stream.
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false);
+ body1->AppendPayload(kBody);
+ body1->EndData();
+ int submit_result = adapter->SubmitResponse(
+ 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}),
+ std::move(body1));
+ EXPECT_EQ(submit_result, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody));
+ visitor.Clear();
+ EXPECT_FALSE(adapter->session().want_write());
+
+ // The body source has been exhausted by the call to Send() above.
+ int trailer_result = adapter->SubmitTrailer(
+ 1, ToHeaders({{"final-status", "a-ok"},
+ {"x-comment", "trailers sure are cool"}}));
+ ASSERT_EQ(trailer_result, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+}
+
+TEST(NgHttp2AdapterTest, ClientSendsContinuation) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ EXPECT_FALSE(adapter->session().want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true,
+ /*add_continuation=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 1));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https"));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4));
+ 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, OnEndStream(1));
+
+ const size_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+}
+
+TEST(NgHttp2AdapterTest, ClientSendsMetadataWithContinuation) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ EXPECT_FALSE(adapter->session().want_write());
+
+ const std::string frames =
+ TestFrameSequence()
+ .ClientPreface()
+ .Metadata(0, "Example connection metadata in multiple frames", true)
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/false,
+ /*add_continuation=*/true)
+ .Metadata(1,
+ "Some stream metadata that's also sent in multiple frames",
+ true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Metadata on stream 0
+ EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 0));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataEndForStream(0));
+
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https"));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ // Metadata on stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 0));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataEndForStream(1));
+
+ const size_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+ EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload(
+ "Example connection metadata in multiple frames"),
+ absl::StrJoin(visitor.GetMetadata(0), ""));
+ EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload(
+ "Some stream metadata that's also sent in multiple frames"),
+ absl::StrJoin(visitor.GetMetadata(1), ""));
+}
+
+TEST(NgHttp2AdapterTest, ServerSendsInvalidTrailers) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ EXPECT_FALSE(adapter->session().want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ 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, OnEndStream(1));
+
+ const ssize_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ const absl::string_view kBody = "This is an example response body.";
+
+ // The body source must indicate that the end of the body is not the end of
+ // the stream.
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false);
+ body1->AppendPayload(kBody);
+ body1->EndData();
+ int submit_result = adapter->SubmitResponse(
+ 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}),
+ std::move(body1));
+ EXPECT_EQ(submit_result, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody));
+ visitor.Clear();
+ EXPECT_FALSE(adapter->session().want_write());
+
+ // The body source has been exhausted by the call to Send() above.
+ int trailer_result =
+ adapter->SubmitTrailer(1, ToHeaders({{":final-status", "a-ok"}}));
+ ASSERT_EQ(trailer_result, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
}
} // namespace
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc
index 8774873c95f..4a2221ea2ee 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc
@@ -6,6 +6,7 @@
#include "absl/strings/string_view.h"
#include "http2/adapter/http2_protocol.h"
#include "http2/adapter/http2_visitor_interface.h"
+#include "http2/adapter/nghttp2_data_provider.h"
#include "http2/adapter/nghttp2_util.h"
#include "third_party/nghttp2/nghttp2.h"
#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
@@ -16,14 +17,33 @@ namespace http2 {
namespace adapter {
namespace callbacks {
+ssize_t OnReadyToSend(nghttp2_session* /* session */, const uint8_t* data,
+ size_t length, int flags, void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ const ssize_t result = visitor->OnReadyToSend(ToStringView(data, length));
+ QUICHE_VLOG(1) << "OnReadyToSend(length=" << length << ", flags=" << flags
+ << ") returning " << result;
+ if (result > 0) {
+ return result;
+ } else if (result == Http2VisitorInterface::kSendBlocked) {
+ return -504; // NGHTTP2_ERR_WOULDBLOCK
+ } else {
+ return -902; // NGHTTP2_ERR_CALLBACK_FAILURE
+ }
+}
+
int OnBeginFrame(nghttp2_session* /* session */,
const nghttp2_frame_hd* header,
void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
visitor->OnFrameHeader(header->stream_id, header->length, header->type,
header->flags);
if (header->type == NGHTTP2_DATA) {
visitor->OnBeginDataForStream(header->stream_id, header->length);
+ } else if (header->type == kMetadataFrameType) {
+ visitor->OnBeginMetadataForStream(header->stream_id, header->length);
}
return 0;
}
@@ -31,10 +51,9 @@ int OnBeginFrame(nghttp2_session* /* session */,
int OnFrameReceived(nghttp2_session* /* session */,
const nghttp2_frame* frame,
void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
const Http2StreamId stream_id = frame->hd.stream_id;
- QUICHE_VLOG(2) << "Frame " << static_cast<int>(frame->hd.type)
- << " for stream " << stream_id;
switch (frame->hd.type) {
// The beginning of the DATA frame is handled in OnBeginFrame(), and the
// beginning of the header block is handled in client/server-specific
@@ -71,7 +90,7 @@ int OnFrameReceived(nghttp2_session* /* session */,
visitor->OnSettingsAck();
} else {
visitor->OnSettingsStart();
- for (int i = 0; i < frame->settings.niv; ++i) {
+ for (size_t i = 0; i < frame->settings.niv; ++i) {
nghttp2_settings_entry entry = frame->settings.iv[i];
// The nghttp2_settings_entry uses int32_t for the ID; we must cast.
visitor->OnSetting(Http2Setting{
@@ -129,29 +148,67 @@ int OnFrameReceived(nghttp2_session* /* session */,
int OnBeginHeaders(nghttp2_session* /* session */,
const nghttp2_frame* frame,
void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
- visitor->OnBeginHeadersForStream(frame->hd.stream_id);
- return 0;
+ const bool result = visitor->OnBeginHeadersForStream(frame->hd.stream_id);
+ return result ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE;
}
-int OnHeader(nghttp2_session* /* session */,
- const nghttp2_frame* frame,
- nghttp2_rcbuf* name,
- nghttp2_rcbuf* value,
- uint8_t flags,
+int OnHeader(nghttp2_session* /* session */, const nghttp2_frame* frame,
+ nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t /*flags*/,
void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
- visitor->OnHeaderForStream(frame->hd.stream_id, ToStringView(name),
- ToStringView(value));
- return 0;
+ const Http2VisitorInterface::OnHeaderResult result =
+ visitor->OnHeaderForStream(frame->hd.stream_id, ToStringView(name),
+ ToStringView(value));
+ switch (result) {
+ case Http2VisitorInterface::HEADER_OK:
+ return 0;
+ case Http2VisitorInterface::HEADER_CONNECTION_ERROR:
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ case Http2VisitorInterface::HEADER_RST_STREAM:
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
}
-int OnDataChunk(nghttp2_session* /* session */,
- uint8_t flags,
- Http2StreamId stream_id,
- const uint8_t* data,
- size_t len,
+int OnBeforeFrameSent(nghttp2_session* /* session */,
+ const nghttp2_frame* frame, void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
+ LogBeforeSend(*frame);
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ return visitor->OnBeforeFrameSent(frame->hd.type, frame->hd.stream_id,
+ frame->hd.length, frame->hd.flags);
+}
+
+int OnFrameSent(nghttp2_session* /* session */, const nghttp2_frame* frame,
void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ uint32_t error_code = 0;
+ if (frame->hd.type == NGHTTP2_RST_STREAM) {
+ error_code = frame->rst_stream.error_code;
+ } else if (frame->hd.type == NGHTTP2_GOAWAY) {
+ error_code = frame->goaway.error_code;
+ }
+ return visitor->OnFrameSent(frame->hd.type, frame->hd.stream_id,
+ frame->hd.length, frame->hd.flags, error_code);
+}
+
+int OnInvalidFrameReceived(nghttp2_session* /* session */,
+ const nghttp2_frame* frame, int lib_error_code,
+ void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ const bool result =
+ visitor->OnInvalidFrame(frame->hd.stream_id, lib_error_code);
+ return result ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE;
+}
+
+int OnDataChunk(nghttp2_session* /* session */, uint8_t /*flags*/,
+ Http2StreamId stream_id, const uint8_t* data, size_t len,
+ void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
visitor->OnDataForStream(
stream_id, absl::string_view(reinterpret_cast<const char*>(data), len));
@@ -162,34 +219,63 @@ int OnStreamClosed(nghttp2_session* /* session */,
Http2StreamId stream_id,
uint32_t error_code,
void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
visitor->OnCloseStream(stream_id, ToHttp2ErrorCode(error_code));
return 0;
}
-ssize_t OnReadyToReadDataForStream(nghttp2_session* /* session */,
- Http2StreamId stream_id,
- uint8_t* dest_buffer,
- size_t max_length,
- uint32_t* data_flags,
- nghttp2_data_source* source,
- void* user_data) {
- auto* visitor = static_cast<Http2VisitorInterface*>(source->ptr);
- ssize_t bytes_to_send = 0;
- bool end_stream = false;
- visitor->OnReadyToSendDataForStream(stream_id,
- reinterpret_cast<char*>(dest_buffer),
- max_length, &bytes_to_send, &end_stream);
- if (bytes_to_send >= 0 && end_stream) {
- *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+int OnExtensionChunkReceived(nghttp2_session* /*session*/,
+ const nghttp2_frame_hd* hd, const uint8_t* data,
+ size_t len, void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ if (hd->type != kMetadataFrameType) {
+ QUICHE_LOG(ERROR) << "Unexpected frame type: "
+ << static_cast<int>(hd->type);
+ return NGHTTP2_ERR_CANCEL;
+ }
+ visitor->OnMetadataForStream(hd->stream_id, ToStringView(data, len));
+ return 0;
+}
+
+int OnUnpackExtensionCallback(nghttp2_session* /*session*/, void** /*payload*/,
+ const nghttp2_frame_hd* hd, void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ if (hd->flags == kMetadataEndFlag) {
+ const bool result = visitor->OnMetadataEndForStream(hd->stream_id);
+ if (!result) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
}
- return bytes_to_send;
+ return 0;
+}
+
+ssize_t OnPackExtensionCallback(nghttp2_session* /*session*/, uint8_t* buf,
+ size_t len, const nghttp2_frame* frame,
+ void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ ssize_t written = 0;
+ visitor->OnReadyToSendMetadataForStream(
+ frame->hd.stream_id, reinterpret_cast<char*>(buf), len, &written);
+ return written;
+}
+
+int OnError(nghttp2_session* /*session*/, int /*lib_error_code*/,
+ const char* msg, size_t len, void* user_data) {
+ QUICHE_CHECK_NE(user_data, nullptr);
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ visitor->OnErrorDebug(absl::string_view(msg, len));
+ return 0;
}
nghttp2_session_callbacks_unique_ptr Create() {
nghttp2_session_callbacks* callbacks;
nghttp2_session_callbacks_new(&callbacks);
+ nghttp2_session_callbacks_set_send_callback(callbacks, &OnReadyToSend);
nghttp2_session_callbacks_set_on_begin_frame_callback(callbacks,
&OnBeginFrame);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
@@ -201,6 +287,21 @@ nghttp2_session_callbacks_unique_ptr Create() {
&OnDataChunk);
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks,
&OnStreamClosed);
+ nghttp2_session_callbacks_set_before_frame_send_callback(callbacks,
+ &OnBeforeFrameSent);
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, &OnFrameSent);
+ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
+ callbacks, &OnInvalidFrameReceived);
+ nghttp2_session_callbacks_set_error_callback2(callbacks, &OnError);
+ // on_frame_not_send_callback <- just ignored
+ nghttp2_session_callbacks_set_send_data_callback(
+ callbacks, &DataFrameSourceSendCallback);
+ nghttp2_session_callbacks_set_pack_extension_callback(
+ callbacks, &OnPackExtensionCallback);
+ nghttp2_session_callbacks_set_unpack_extension_callback(
+ callbacks, &OnUnpackExtensionCallback);
+ nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(
+ callbacks, &OnExtensionChunkReceived);
return MakeCallbacksPtr(callbacks);
}
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h
index 5fbaee041c5..696b6847509 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h
@@ -13,6 +13,13 @@ namespace callbacks {
// beginning of its lifetime. It is expected that |user_data| holds an
// Http2VisitorInterface.
+// Callback once the library is ready to send serialized frames.
+ssize_t OnReadyToSend(nghttp2_session* session,
+ const uint8_t* data,
+ size_t length,
+ int flags,
+ void* user_data);
+
// Callback once a frame header has been received.
int OnBeginFrame(nghttp2_session* session, const nghttp2_frame_hd* header,
void* user_data);
@@ -31,7 +38,19 @@ int OnHeader(nghttp2_session* session, const nghttp2_frame* frame,
nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags,
void* user_data);
-// Callback once a chunk of data (from a DATA frame payload) has been received.
+// Invoked immediately before sending a frame.
+int OnBeforeFrameSent(nghttp2_session* session, const nghttp2_frame* frame,
+ void* user_data);
+
+// Invoked immediately after a frame is sent.
+int OnFrameSent(nghttp2_session* session, const nghttp2_frame* frame,
+ void* user_data);
+
+// Invoked when an invalid frame is received.
+int OnInvalidFrameReceived(nghttp2_session* session, const nghttp2_frame* frame,
+ int lib_error_code, void* user_data);
+
+// Invoked when a chunk of data (from a DATA frame payload) has been received.
int OnDataChunk(nghttp2_session* session, uint8_t flags,
Http2StreamId stream_id, const uint8_t* data, size_t len,
void* user_data);
@@ -40,13 +59,25 @@ int OnDataChunk(nghttp2_session* session, uint8_t flags,
int OnStreamClosed(nghttp2_session* session, Http2StreamId stream_id,
uint32_t error_code, void* user_data);
-// Callback once nghttp2 is ready to read data from |source| into |dest_buffer|.
-ssize_t OnReadyToReadDataForStream(nghttp2_session* session,
- Http2StreamId stream_id,
- uint8_t* dest_buffer, size_t max_length,
- uint32_t* data_flags,
- nghttp2_data_source* source,
- void* user_data);
+// Invoked when nghttp2 has a chunk of extension frame data to pass to the
+// application.
+int OnExtensionChunkReceived(nghttp2_session* session,
+ const nghttp2_frame_hd* hd, const uint8_t* data,
+ size_t len, void* user_data);
+
+// Invoked when nghttp2 wants the application to unpack an extension payload.
+int OnUnpackExtensionCallback(nghttp2_session* session, void** payload,
+ const nghttp2_frame_hd* hd, void* user_data);
+
+// Invoked when nghttp2 is ready to pack an extension payload. Returns the
+// number of bytes serialized to |buf|.
+ssize_t OnPackExtensionCallback(nghttp2_session* session, uint8_t* buf,
+ size_t len, const nghttp2_frame* frame,
+ void* user_data);
+
+// Invoked when the library has an error message to deliver.
+int OnError(nghttp2_session* session, int lib_error_code, const char* msg,
+ size_t len, void* user_data);
nghttp2_session_callbacks_unique_ptr Create();
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.cc
new file mode 100644
index 00000000000..200aa67ae29
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.cc
@@ -0,0 +1,63 @@
+#include "http2/adapter/nghttp2_data_provider.h"
+
+#include "http2/adapter/http2_visitor_interface.h"
+#include "http2/adapter/nghttp2_util.h"
+
+namespace http2 {
+namespace adapter {
+namespace callbacks {
+
+namespace {
+const size_t kFrameHeaderSize = 9;
+}
+
+ssize_t DataFrameSourceReadCallback(nghttp2_session* /* session */,
+ int32_t /* stream_id */,
+ uint8_t* /* buf */,
+ size_t length,
+ uint32_t* data_flags,
+ nghttp2_data_source* source,
+ void* /* user_data */) {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+ auto* frame_source = static_cast<DataFrameSource*>(source->ptr);
+ auto [result_length, done] = frame_source->SelectPayloadLength(length);
+ if (result_length == 0 && !done) {
+ return NGHTTP2_ERR_DEFERRED;
+ } else if (result_length == DataFrameSource::kError) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ if (done) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+ if (!frame_source->send_fin()) {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ return result_length;
+}
+
+int DataFrameSourceSendCallback(nghttp2_session* /* session */,
+ nghttp2_frame* /* frame */,
+ const uint8_t* framehd,
+ size_t length,
+ nghttp2_data_source* source,
+ void* /* user_data */) {
+ auto* frame_source = static_cast<DataFrameSource*>(source->ptr);
+ frame_source->Send(ToStringView(framehd, kFrameHeaderSize), length);
+ return 0;
+}
+
+} // namespace callbacks
+
+std::unique_ptr<nghttp2_data_provider> MakeDataProvider(
+ DataFrameSource* source) {
+ if (source == nullptr) {
+ return nullptr;
+ }
+ auto provider = absl::make_unique<nghttp2_data_provider>();
+ provider->source.ptr = source;
+ provider->read_callback = &callbacks::DataFrameSourceReadCallback;
+ return provider;
+}
+
+} // namespace adapter
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.h
new file mode 100644
index 00000000000..241bab91cdd
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.h
@@ -0,0 +1,37 @@
+#ifndef QUICHE_HTTP2_ADAPTER_NGHTTP2_DATA_PROVIDER_H_
+#define QUICHE_HTTP2_ADAPTER_NGHTTP2_DATA_PROVIDER_H_
+
+#include "http2/adapter/data_source.h"
+#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+
+namespace http2 {
+namespace adapter {
+namespace callbacks {
+
+// Assumes |source| is a DataFrameSource.
+ssize_t DataFrameSourceReadCallback(nghttp2_session* /*session */,
+ int32_t /* stream_id */,
+ uint8_t* /* buf */,
+ size_t length,
+ uint32_t* data_flags,
+ nghttp2_data_source* source,
+ void* /* user_data */);
+
+int DataFrameSourceSendCallback(nghttp2_session* /* session */,
+ nghttp2_frame* /* frame */,
+ const uint8_t* framehd,
+ size_t length,
+ nghttp2_data_source* source,
+ void* /* user_data */);
+
+} // namespace callbacks
+
+// Transforms a DataFrameSource into a nghttp2_data_provider. Does not take
+// ownership of |source|. Returns nullptr if |source| is nullptr.
+std::unique_ptr<nghttp2_data_provider> MakeDataProvider(
+ DataFrameSource* source);
+
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_NGHTTP2_DATA_PROVIDER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider_test.cc
new file mode 100644
index 00000000000..af8d98187f2
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider_test.cc
@@ -0,0 +1,117 @@
+#include "http2/adapter/nghttp2_data_provider.h"
+
+#include "http2/adapter/test_utils.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+
+const size_t kFrameHeaderSize = 9;
+
+// Verifies that a nghttp2_data_provider derived from a DataFrameSource works
+// correctly with nghttp2-style callbacks when the amount of data read is less
+// than what the source provides.
+TEST(DataProviderTest, ReadLessThanSourceProvides) {
+ DataSavingVisitor visitor;
+ TestDataFrameSource source(visitor, true);
+ source.AppendPayload("Example payload");
+ source.EndData();
+ auto provider = MakeDataProvider(&source);
+ uint32_t data_flags = 0;
+ const int32_t kStreamId = 1;
+ const size_t kReadLength = 10;
+ // Read callback selects a payload length given an upper bound.
+ ssize_t result =
+ provider->read_callback(nullptr, kStreamId, nullptr, kReadLength,
+ &data_flags, &provider->source, nullptr);
+ ASSERT_EQ(kReadLength, result);
+ EXPECT_EQ(NGHTTP2_DATA_FLAG_NO_COPY, data_flags);
+
+ const uint8_t framehd[kFrameHeaderSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ // Sends the frame header and some payload bytes.
+ int send_result = callbacks::DataFrameSourceSendCallback(
+ nullptr, nullptr, framehd, result, &provider->source, nullptr);
+ EXPECT_EQ(0, send_result);
+ // Data accepted by the visitor includes a frame header and kReadLength bytes
+ // of payload.
+ EXPECT_EQ(visitor.data().size(), kFrameHeaderSize + kReadLength);
+}
+
+// Verifies that a nghttp2_data_provider derived from a DataFrameSource works
+// correctly with nghttp2-style callbacks when the amount of data read is more
+// than what the source provides.
+TEST(DataProviderTest, ReadMoreThanSourceProvides) {
+ DataSavingVisitor visitor;
+ const absl::string_view kPayload = "Example payload";
+ TestDataFrameSource source(visitor, true);
+ source.AppendPayload(kPayload);
+ source.EndData();
+ auto provider = MakeDataProvider(&source);
+ uint32_t data_flags = 0;
+ const int32_t kStreamId = 1;
+ const size_t kReadLength = 30;
+ // Read callback selects a payload length given an upper bound.
+ ssize_t result =
+ provider->read_callback(nullptr, kStreamId, nullptr, kReadLength,
+ &data_flags, &provider->source, nullptr);
+ ASSERT_EQ(kPayload.size(), result);
+ EXPECT_EQ(NGHTTP2_DATA_FLAG_NO_COPY | NGHTTP2_DATA_FLAG_EOF, data_flags);
+
+ const uint8_t framehd[kFrameHeaderSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ // Sends the frame header and some payload bytes.
+ int send_result = callbacks::DataFrameSourceSendCallback(
+ nullptr, nullptr, framehd, result, &provider->source, nullptr);
+ EXPECT_EQ(0, send_result);
+ // Data accepted by the visitor includes a frame header and the entire
+ // payload.
+ EXPECT_EQ(visitor.data().size(), kFrameHeaderSize + kPayload.size());
+}
+
+// Verifies that a nghttp2_data_provider derived from a DataFrameSource works
+// correctly with nghttp2-style callbacks when the source is blocked.
+TEST(DataProviderTest, ReadFromBlockedSource) {
+ DataSavingVisitor visitor;
+ // Source has no payload, but also no fin, so it's blocked.
+ TestDataFrameSource source(visitor, false);
+ auto provider = MakeDataProvider(&source);
+ uint32_t data_flags = 0;
+ const int32_t kStreamId = 1;
+ const size_t kReadLength = 10;
+ ssize_t result =
+ provider->read_callback(nullptr, kStreamId, nullptr, kReadLength,
+ &data_flags, &provider->source, nullptr);
+ // Read operation is deferred, since the source is blocked.
+ EXPECT_EQ(NGHTTP2_ERR_DEFERRED, result);
+}
+
+// Verifies that a nghttp2_data_provider derived from a DataFrameSource works
+// correctly with nghttp2-style callbacks when the source provides only fin and
+// no data.
+TEST(DataProviderTest, ReadFromZeroLengthSource) {
+ DataSavingVisitor visitor;
+ // Empty payload and fin=true indicates the source is done.
+ TestDataFrameSource source(visitor, true);
+ source.EndData();
+ auto provider = MakeDataProvider(&source);
+ uint32_t data_flags = 0;
+ const int32_t kStreamId = 1;
+ const size_t kReadLength = 10;
+ ssize_t result =
+ provider->read_callback(nullptr, kStreamId, nullptr, kReadLength,
+ &data_flags, &provider->source, nullptr);
+ ASSERT_EQ(0, result);
+ EXPECT_EQ(NGHTTP2_DATA_FLAG_NO_COPY | NGHTTP2_DATA_FLAG_EOF, data_flags);
+
+ const uint8_t framehd[kFrameHeaderSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ int send_result = callbacks::DataFrameSourceSendCallback(
+ nullptr, nullptr, framehd, result, &provider->source, nullptr);
+ EXPECT_EQ(0, send_result);
+ // Data accepted by the visitor includes a frame header with fin and zero
+ // bytes of payload.
+ EXPECT_EQ(visitor.data().size(), kFrameHeaderSize);
+}
+
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc
index d434b06e0f5..9868958bc67 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc
@@ -4,35 +4,21 @@
namespace http2 {
namespace adapter {
-namespace {
-
-void DeleteOptions(nghttp2_option* options) {
- if (options) {
- nghttp2_option_del(options);
- }
-}
-
-} // namespace
NgHttp2Session::NgHttp2Session(Perspective perspective,
nghttp2_session_callbacks_unique_ptr callbacks,
- nghttp2_option* options,
- void* userdata)
- : session_(MakeSessionPtr(nullptr)),
- options_(options, DeleteOptions),
- perspective_(perspective) {
+ const nghttp2_option* options, void* userdata)
+ : session_(MakeSessionPtr(nullptr)), perspective_(perspective) {
nghttp2_session* session;
switch (perspective) {
case Perspective::kClient:
- nghttp2_session_client_new2(&session, callbacks.get(), userdata,
- options_.get());
+ nghttp2_session_client_new2(&session, callbacks.get(), userdata, options);
break;
case Perspective::kServer:
- nghttp2_session_server_new2(&session, callbacks.get(), userdata,
- options_.get());
+ nghttp2_session_server_new2(&session, callbacks.get(), userdata, options);
break;
}
- session_.reset(session);
+ session_ = MakeSessionPtr(session);
}
NgHttp2Session::~NgHttp2Session() {
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h
index d446a07c75b..4339875588c 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h
@@ -4,18 +4,18 @@
#include "http2/adapter/http2_session.h"
#include "http2/adapter/nghttp2_util.h"
#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
// A C++ wrapper around common nghttp2_session operations.
-class NgHttp2Session : public Http2Session {
+class QUICHE_EXPORT_PRIVATE NgHttp2Session : public Http2Session {
public:
- // Takes ownership of |options|.
+ // Does not take ownership of |options|.
NgHttp2Session(Perspective perspective,
nghttp2_session_callbacks_unique_ptr callbacks,
- nghttp2_option* options,
- void* userdata);
+ const nghttp2_option* options, void* userdata);
~NgHttp2Session() override;
ssize_t ProcessBytes(absl::string_view bytes) override;
@@ -29,10 +29,7 @@ class NgHttp2Session : public Http2Session {
nghttp2_session* raw_ptr() const { return session_.get(); }
private:
- using OptionsDeleter = void (&)(nghttp2_option*);
-
nghttp2_session_unique_ptr session_;
- std::unique_ptr<nghttp2_option, OptionsDeleter> options_;
Perspective perspective_;
};
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc
index 169d2f20dd0..487843bcc52 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc
@@ -26,47 +26,36 @@ enum FrameType {
WINDOW_UPDATE,
};
-ssize_t SaveSessionOutput(nghttp2_session* /* session*/,
- const uint8_t* data,
- size_t length,
- int /* flags */,
- void* user_data) {
- auto visitor = static_cast<DataSavingVisitor*>(user_data);
- visitor->Save(ToStringView(data, length));
- return length;
-}
-
class NgHttp2SessionTest : public testing::Test {
public:
- nghttp2_option* CreateOptions() {
- nghttp2_option* options;
- nghttp2_option_new(&options);
- nghttp2_option_set_no_auto_window_update(options, 1);
- return options;
+ void SetUp() override {
+ nghttp2_option_new(&options_);
+ nghttp2_option_set_no_auto_window_update(options_, 1);
}
+ void TearDown() override { nghttp2_option_del(options_); }
+
nghttp2_session_callbacks_unique_ptr CreateCallbacks() {
nghttp2_session_callbacks_unique_ptr callbacks = callbacks::Create();
- nghttp2_session_callbacks_set_send_callback(callbacks.get(),
- &SaveSessionOutput);
return callbacks;
}
DataSavingVisitor visitor_;
+ nghttp2_option* options_ = nullptr;
};
TEST_F(NgHttp2SessionTest, ClientConstruction) {
- NgHttp2Session session(Perspective::kClient, CreateCallbacks(),
- CreateOptions(), &visitor_);
+ NgHttp2Session session(Perspective::kClient, CreateCallbacks(), options_,
+ &visitor_);
EXPECT_TRUE(session.want_read());
EXPECT_FALSE(session.want_write());
- EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize);
+ EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize);
EXPECT_NE(session.raw_ptr(), nullptr);
}
TEST_F(NgHttp2SessionTest, ClientHandlesFrames) {
- NgHttp2Session session(Perspective::kClient, CreateCallbacks(),
- CreateOptions(), &visitor_);
+ NgHttp2Session session(Perspective::kClient, CreateCallbacks(), options_,
+ &visitor_);
ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr()));
ASSERT_GT(visitor_.data().size(), 0);
@@ -92,7 +81,13 @@ TEST_F(NgHttp2SessionTest, ClientHandlesFrames) {
EXPECT_EQ(initial_frames.size(), initial_result);
EXPECT_EQ(session.GetRemoteWindowSize(),
- kDefaultInitialStreamWindowSize + 1000);
+ kInitialFlowControlWindowSize + 1000);
+
+ EXPECT_CALL(visitor_, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor_, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1));
+ EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0));
+
ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr()));
// Some bytes should have been serialized.
absl::string_view serialized = visitor_.data();
@@ -139,6 +134,13 @@ TEST_F(NgHttp2SessionTest, ClientHandlesFrames) {
ASSERT_GT(stream_id3, 0);
QUICHE_LOG(INFO) << "Created stream: " << stream_id3;
+ EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+ EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+ EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 3, _, 0x5));
+ EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 3, _, 0x5, 0));
+ EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 5, _, 0x5));
+ EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 5, _, 0x5, 0));
+
ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr()));
serialized = visitor_.data();
EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS,
@@ -203,17 +205,17 @@ TEST_F(NgHttp2SessionTest, ClientHandlesFrames) {
}
TEST_F(NgHttp2SessionTest, ServerConstruction) {
- NgHttp2Session session(Perspective::kServer, CreateCallbacks(),
- CreateOptions(), &visitor_);
+ NgHttp2Session session(Perspective::kServer, CreateCallbacks(), options_,
+ &visitor_);
EXPECT_TRUE(session.want_read());
EXPECT_FALSE(session.want_write());
- EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize);
+ EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize);
EXPECT_NE(session.raw_ptr(), nullptr);
}
TEST_F(NgHttp2SessionTest, ServerHandlesFrames) {
- NgHttp2Session session(Perspective::kServer, CreateCallbacks(),
- CreateOptions(), &visitor_);
+ NgHttp2Session session(Perspective::kServer, CreateCallbacks(), options_,
+ &visitor_);
const std::string frames = TestFrameSequence()
.ClientPreface()
@@ -277,7 +279,14 @@ TEST_F(NgHttp2SessionTest, ServerHandlesFrames) {
EXPECT_EQ(frames.size(), result);
EXPECT_EQ(session.GetRemoteWindowSize(),
- kDefaultInitialStreamWindowSize + 1000);
+ kInitialFlowControlWindowSize + 1000);
+
+ EXPECT_CALL(visitor_, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor_, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1));
+ EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0));
+ EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1));
+ EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0));
EXPECT_TRUE(session.want_write());
ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr()));
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test.cc
new file mode 100644
index 00000000000..0b977dc1815
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test.cc
@@ -0,0 +1,205 @@
+#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+
+#include "absl/strings/str_cat.h"
+#include "http2/adapter/mock_nghttp2_callbacks.h"
+#include "http2/adapter/nghttp2_test_utils.h"
+#include "http2/adapter/nghttp2_util.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,
+};
+
+nghttp2_option* GetOptions() {
+ 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);
+ return options;
+}
+
+class Nghttp2Test : public testing::Test {
+ public:
+ Nghttp2Test() : session_(MakeSessionPtr(nullptr)) {}
+
+ void SetUp() override { InitializeSession(); }
+
+ virtual Perspective GetPerspective() = 0;
+
+ void InitializeSession() {
+ auto nghttp2_callbacks = MockNghttp2Callbacks::GetCallbacks();
+ nghttp2_option* options = GetOptions();
+ nghttp2_session* ptr;
+ if (GetPerspective() == Perspective::kClient) {
+ nghttp2_session_client_new2(&ptr, nghttp2_callbacks.get(),
+ &mock_callbacks_, options);
+ } else {
+ nghttp2_session_server_new2(&ptr, nghttp2_callbacks.get(),
+ &mock_callbacks_, options);
+ }
+ nghttp2_option_del(options);
+
+ // Sets up the Send() callback to append to |serialized_|.
+ EXPECT_CALL(mock_callbacks_, Send(_, _, _))
+ .WillRepeatedly(
+ [this](const uint8_t* data, size_t length, int /*flags*/) {
+ absl::StrAppend(&serialized_, ToStringView(data, length));
+ return length;
+ });
+ // Sets up the SendData() callback to fetch and append data from a
+ // TestDataSource.
+ EXPECT_CALL(mock_callbacks_, SendData(_, _, _, _))
+ .WillRepeatedly([this](nghttp2_frame* /*frame*/, const uint8_t* framehd,
+ size_t length, nghttp2_data_source* source) {
+ QUICHE_LOG(INFO) << "Appending frame header and " << length
+ << " bytes of data";
+ auto* s = static_cast<TestDataSource*>(source->ptr);
+ absl::StrAppend(&serialized_, ToStringView(framehd, 9),
+ s->ReadNext(length));
+ return 0;
+ });
+ session_ = MakeSessionPtr(ptr);
+ }
+
+ testing::StrictMock<MockNghttp2Callbacks> mock_callbacks_;
+ nghttp2_session_unique_ptr session_;
+ std::string serialized_;
+};
+
+class Nghttp2ClientTest : public Nghttp2Test {
+ public:
+ Perspective GetPerspective() override { return Perspective::kClient; }
+};
+
+// Verifies nghttp2 behavior when acting as a client.
+TEST_F(Nghttp2ClientTest, ClientReceivesUnexpectedHeaders) {
+ const std::string initial_frames = TestFrameSequence()
+ .ServerPreface()
+ .Ping(42)
+ .WindowUpdate(0, 1000)
+ .Serialize();
+
+ testing::InSequence seq;
+ EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
+ EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));
+ EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, PING, 0)));
+ EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsPing(42)));
+ EXPECT_CALL(mock_callbacks_,
+ OnBeginFrame(HasFrameHeader(0, WINDOW_UPDATE, 0)));
+ EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsWindowUpdate(1000)));
+
+ ssize_t result = nghttp2_session_mem_recv(
+ session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
+ ASSERT_EQ(result, initial_frames.size());
+
+ const std::string unexpected_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(mock_callbacks_, OnBeginFrame(HasFrameHeader(1, HEADERS, _)));
+ EXPECT_CALL(mock_callbacks_, OnInvalidFrameRecv(IsHeaders(1, _, _), _));
+ // No events from the DATA, RST_STREAM or GOAWAY.
+
+ nghttp2_session_mem_recv(session_.get(),
+ ToUint8Ptr(unexpected_stream_frames.data()),
+ unexpected_stream_frames.size());
+}
+
+// Tests the request-sending behavior of nghttp2 when acting as a client.
+TEST_F(Nghttp2ClientTest, ClientSendsRequest) {
+ int result = nghttp2_session_send(session_.get());
+ ASSERT_EQ(result, 0);
+
+ EXPECT_THAT(serialized_, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized_.clear();
+
+ const std::string initial_frames =
+ TestFrameSequence().ServerPreface().Serialize();
+ testing::InSequence s;
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
+ EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));
+
+ ssize_t recv_result = nghttp2_session_mem_recv(
+ session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
+ EXPECT_EQ(initial_frames.size(), recv_result);
+
+ // Client wants to send a SETTINGS ack.
+ EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsSettings(testing::IsEmpty())));
+ EXPECT_CALL(mock_callbacks_, OnFrameSend(IsSettings(testing::IsEmpty())));
+ EXPECT_TRUE(nghttp2_session_want_write(session_.get()));
+ result = nghttp2_session_send(session_.get());
+ EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+ serialized_.clear();
+
+ EXPECT_FALSE(nghttp2_session_want_write(session_.get()));
+
+ // The following sets up the client request.
+ std::vector<std::pair<absl::string_view, absl::string_view>> headers = {
+ {":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}};
+ std::vector<nghttp2_nv> nvs;
+ for (const auto& h : headers) {
+ nvs.push_back({.name = ToUint8Ptr(h.first.data()),
+ .value = ToUint8Ptr(h.second.data()),
+ .namelen = h.first.size(),
+ .valuelen = h.second.size()});
+ }
+ const absl::string_view kBody = "This is an example request body.";
+ TestDataSource source{kBody};
+ nghttp2_data_provider provider = source.MakeDataProvider();
+ // After submitting the request, the client will want to write.
+ int stream_id =
+ nghttp2_submit_request(session_.get(), nullptr /* pri_spec */, nvs.data(),
+ nvs.size(), &provider, nullptr /* stream_data */);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(nghttp2_session_want_write(session_.get()));
+
+ // We expect that the client will want to write HEADERS, then DATA.
+ EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsHeaders(stream_id, _, _)));
+ EXPECT_CALL(mock_callbacks_, OnFrameSend(IsHeaders(stream_id, _, _)));
+ EXPECT_CALL(mock_callbacks_, OnFrameSend(IsData(stream_id, kBody.size(), _)));
+ nghttp2_session_send(session_.get());
+ EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ EXPECT_THAT(serialized_, testing::HasSubstr(kBody));
+
+ // Once the request is flushed, the client no longer wants to write.
+ EXPECT_FALSE(nghttp2_session_want_write(session_.get()));
+}
+
+} // namespace
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.cc
new file mode 100644
index 00000000000..d9a04f27772
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.cc
@@ -0,0 +1,454 @@
+#include "http2/adapter/nghttp2_test_utils.h"
+
+#include "http2/adapter/nghttp2_util.h"
+#include "common/quiche_endian.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+
+namespace {
+
+// Custom gMock matcher, used to implement HasFrameHeader().
+class FrameHeaderMatcher {
+ public:
+ FrameHeaderMatcher(int32_t streamid, uint8_t type,
+ const testing::Matcher<int> flags)
+ : stream_id_(streamid), type_(type), flags_(flags) {}
+
+ bool Match(const nghttp2_frame_hd& frame,
+ testing::MatchResultListener* listener) const {
+ bool matched = true;
+ if (stream_id_ != frame.stream_id) {
+ *listener << "; expected stream " << stream_id_ << ", saw "
+ << frame.stream_id;
+ matched = false;
+ }
+ if (type_ != frame.type) {
+ *listener << "; expected frame type " << type_ << ", saw "
+ << static_cast<int>(frame.type);
+ matched = false;
+ }
+ if (!flags_.MatchAndExplain(frame.flags, listener)) {
+ matched = false;
+ }
+ return matched;
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "contains a frame header with stream " << stream_id_ << ", type "
+ << type_ << ", ";
+ flags_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const {
+ *os << "does not contain a frame header with stream " << stream_id_
+ << ", type " << type_ << ", ";
+ flags_.DescribeNegationTo(os);
+ }
+
+ private:
+ const int32_t stream_id_;
+ const int type_;
+ const testing::Matcher<int> flags_;
+};
+
+class PointerToFrameHeaderMatcher
+ : public FrameHeaderMatcher,
+ public testing::MatcherInterface<const nghttp2_frame_hd*> {
+ public:
+ PointerToFrameHeaderMatcher(int32_t streamid, uint8_t type,
+ const testing::Matcher<int> flags)
+ : FrameHeaderMatcher(streamid, type, flags) {}
+
+ bool MatchAndExplain(const nghttp2_frame_hd* frame,
+ testing::MatchResultListener* listener) const override {
+ return FrameHeaderMatcher::Match(*frame, listener);
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ FrameHeaderMatcher::DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ FrameHeaderMatcher::DescribeNegationTo(os);
+ }
+};
+
+class ReferenceToFrameHeaderMatcher
+ : public FrameHeaderMatcher,
+ public testing::MatcherInterface<const nghttp2_frame_hd&> {
+ public:
+ ReferenceToFrameHeaderMatcher(int32_t streamid, uint8_t type,
+ const testing::Matcher<int> flags)
+ : FrameHeaderMatcher(streamid, type, flags) {}
+
+ bool MatchAndExplain(const nghttp2_frame_hd& frame,
+ testing::MatchResultListener* listener) const override {
+ return FrameHeaderMatcher::Match(frame, listener);
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ FrameHeaderMatcher::DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ FrameHeaderMatcher::DescribeNegationTo(os);
+ }
+};
+
+class DataMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
+ public:
+ DataMatcher(const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<size_t> length,
+ const testing::Matcher<int> flags)
+ : stream_id_(stream_id), length_(length), flags_(flags) {}
+
+ bool MatchAndExplain(const nghttp2_frame* frame,
+ testing::MatchResultListener* listener) const override {
+ if (frame->hd.type != NGHTTP2_DATA) {
+ *listener << "; expected DATA frame, saw frame of type "
+ << static_cast<int>(frame->hd.type);
+ return false;
+ }
+ bool matched = true;
+ if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
+ matched = false;
+ }
+ if (!length_.MatchAndExplain(frame->hd.length, listener)) {
+ matched = false;
+ }
+ if (!flags_.MatchAndExplain(frame->hd.flags, listener)) {
+ matched = false;
+ }
+ return matched;
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "contains a DATA frame, ";
+ stream_id_.DescribeTo(os);
+ length_.DescribeTo(os);
+ flags_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not contain a DATA frame, ";
+ stream_id_.DescribeNegationTo(os);
+ length_.DescribeNegationTo(os);
+ flags_.DescribeNegationTo(os);
+ }
+
+ private:
+ const testing::Matcher<uint32_t> stream_id_;
+ const testing::Matcher<size_t> length_;
+ const testing::Matcher<int> flags_;
+};
+
+class HeadersMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
+ public:
+ HeadersMatcher(const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<int> flags,
+ const testing::Matcher<int> category)
+ : stream_id_(stream_id), flags_(flags), category_(category) {}
+
+ bool MatchAndExplain(const nghttp2_frame* frame,
+ testing::MatchResultListener* listener) const override {
+ if (frame->hd.type != NGHTTP2_HEADERS) {
+ *listener << "; expected HEADERS frame, saw frame of type "
+ << static_cast<int>(frame->hd.type);
+ return false;
+ }
+ bool matched = true;
+ if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
+ matched = false;
+ }
+ if (!flags_.MatchAndExplain(frame->hd.flags, listener)) {
+ matched = false;
+ }
+ if (!category_.MatchAndExplain(frame->headers.cat, listener)) {
+ matched = false;
+ }
+ return matched;
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "contains a HEADERS frame, ";
+ stream_id_.DescribeTo(os);
+ flags_.DescribeTo(os);
+ category_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not contain a HEADERS frame, ";
+ stream_id_.DescribeNegationTo(os);
+ flags_.DescribeNegationTo(os);
+ category_.DescribeNegationTo(os);
+ }
+
+ private:
+ const testing::Matcher<uint32_t> stream_id_;
+ const testing::Matcher<int> flags_;
+ const testing::Matcher<int> category_;
+};
+
+class RstStreamMatcher
+ : public testing::MatcherInterface<const nghttp2_frame*> {
+ public:
+ RstStreamMatcher(const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<uint32_t> error_code)
+ : stream_id_(stream_id), error_code_(error_code) {}
+
+ bool MatchAndExplain(const nghttp2_frame* frame,
+ testing::MatchResultListener* listener) const override {
+ if (frame->hd.type != NGHTTP2_RST_STREAM) {
+ *listener << "; expected RST_STREAM frame, saw frame of type "
+ << static_cast<int>(frame->hd.type);
+ return false;
+ }
+ bool matched = true;
+ if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
+ matched = false;
+ }
+ if (!error_code_.MatchAndExplain(frame->rst_stream.error_code, listener)) {
+ matched = false;
+ }
+ return matched;
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "contains a RST_STREAM frame, ";
+ stream_id_.DescribeTo(os);
+ error_code_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not contain a RST_STREAM frame, ";
+ stream_id_.DescribeNegationTo(os);
+ error_code_.DescribeNegationTo(os);
+ }
+
+ private:
+ const testing::Matcher<uint32_t> stream_id_;
+ const testing::Matcher<uint32_t> error_code_;
+};
+
+class SettingsMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
+ public:
+ SettingsMatcher(const testing::Matcher<std::vector<Http2Setting>> values)
+ : values_(values) {}
+
+ bool MatchAndExplain(const nghttp2_frame* frame,
+ testing::MatchResultListener* listener) const override {
+ if (frame->hd.type != NGHTTP2_SETTINGS) {
+ *listener << "; expected SETTINGS frame, saw frame of type "
+ << static_cast<int>(frame->hd.type);
+ return false;
+ }
+ std::vector<Http2Setting> settings;
+ settings.reserve(frame->settings.niv);
+ for (size_t i = 0; i < frame->settings.niv; ++i) {
+ const auto& p = frame->settings.iv[i];
+ settings.push_back({static_cast<uint16_t>(p.settings_id), p.value});
+ }
+ return values_.MatchAndExplain(settings, listener);
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "contains a SETTINGS frame, ";
+ values_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not contain a SETTINGS frame, ";
+ values_.DescribeNegationTo(os);
+ }
+
+ private:
+ const testing::Matcher<std::vector<Http2Setting>> values_;
+};
+
+class PingMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
+ public:
+ PingMatcher(const testing::Matcher<uint64_t> id, bool is_ack)
+ : id_(id), is_ack_(is_ack) {}
+
+ bool MatchAndExplain(const nghttp2_frame* frame,
+ testing::MatchResultListener* listener) const override {
+ if (frame->hd.type != NGHTTP2_PING) {
+ *listener << "; expected PING frame, saw frame of type "
+ << static_cast<int>(frame->hd.type);
+ return false;
+ }
+ bool matched = true;
+ bool frame_ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
+ if (is_ack_ != frame_ack) {
+ *listener << "; expected is_ack=" << is_ack_ << ", saw " << frame_ack;
+ matched = false;
+ }
+ uint64_t data;
+ std::memcpy(&data, frame->ping.opaque_data, sizeof(data));
+ data = quiche::QuicheEndian::HostToNet64(data);
+ if (!id_.MatchAndExplain(data, listener)) {
+ matched = false;
+ }
+ return matched;
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "contains a PING frame, ";
+ id_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not contain a PING frame, ";
+ id_.DescribeNegationTo(os);
+ }
+
+ private:
+ const testing::Matcher<uint64_t> id_;
+ const bool is_ack_;
+};
+
+class GoAwayMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
+ public:
+ GoAwayMatcher(const testing::Matcher<uint32_t> last_stream_id,
+ const testing::Matcher<uint32_t> error_code,
+ const testing::Matcher<absl::string_view> opaque_data)
+ : last_stream_id_(last_stream_id),
+ error_code_(error_code),
+ opaque_data_(opaque_data) {}
+
+ bool MatchAndExplain(const nghttp2_frame* frame,
+ testing::MatchResultListener* listener) const override {
+ if (frame->hd.type != NGHTTP2_GOAWAY) {
+ *listener << "; expected GOAWAY frame, saw frame of type "
+ << static_cast<int>(frame->hd.type);
+ return false;
+ }
+ bool matched = true;
+ if (!last_stream_id_.MatchAndExplain(frame->goaway.last_stream_id,
+ listener)) {
+ matched = false;
+ }
+ if (!error_code_.MatchAndExplain(frame->goaway.error_code, listener)) {
+ matched = false;
+ }
+ auto opaque_data =
+ ToStringView(frame->goaway.opaque_data, frame->goaway.opaque_data_len);
+ if (!opaque_data_.MatchAndExplain(opaque_data, listener)) {
+ matched = false;
+ }
+ return matched;
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "contains a GOAWAY frame, ";
+ last_stream_id_.DescribeTo(os);
+ error_code_.DescribeTo(os);
+ opaque_data_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not contain a GOAWAY frame, ";
+ last_stream_id_.DescribeNegationTo(os);
+ error_code_.DescribeNegationTo(os);
+ opaque_data_.DescribeNegationTo(os);
+ }
+
+ private:
+ const testing::Matcher<uint32_t> last_stream_id_;
+ const testing::Matcher<uint32_t> error_code_;
+ const testing::Matcher<absl::string_view> opaque_data_;
+};
+
+class WindowUpdateMatcher
+ : public testing::MatcherInterface<const nghttp2_frame*> {
+ public:
+ WindowUpdateMatcher(const testing::Matcher<uint32_t> delta) : delta_(delta) {}
+
+ bool MatchAndExplain(const nghttp2_frame* frame,
+ testing::MatchResultListener* listener) const override {
+ if (frame->hd.type != NGHTTP2_WINDOW_UPDATE) {
+ *listener << "; expected WINDOW_UPDATE frame, saw frame of type "
+ << static_cast<int>(frame->hd.type);
+ return false;
+ }
+ return delta_.MatchAndExplain(frame->window_update.window_size_increment,
+ listener);
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "contains a WINDOW_UPDATE frame, ";
+ delta_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not contain a WINDOW_UPDATE frame, ";
+ delta_.DescribeNegationTo(os);
+ }
+
+ private:
+ const testing::Matcher<uint32_t> delta_;
+};
+
+} // namespace
+
+testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader(
+ uint32_t streamid, uint8_t type, const testing::Matcher<int> flags) {
+ return MakeMatcher(new PointerToFrameHeaderMatcher(streamid, type, flags));
+}
+
+testing::Matcher<const nghttp2_frame_hd&> HasFrameHeaderRef(
+ uint32_t streamid, uint8_t type, const testing::Matcher<int> flags) {
+ return MakeMatcher(new ReferenceToFrameHeaderMatcher(streamid, type, flags));
+}
+
+testing::Matcher<const nghttp2_frame*> IsData(
+ const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<size_t> length, const testing::Matcher<int> flags) {
+ return MakeMatcher(new DataMatcher(stream_id, length, flags));
+}
+
+testing::Matcher<const nghttp2_frame*> IsHeaders(
+ const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<int> flags, const testing::Matcher<int> category) {
+ return MakeMatcher(new HeadersMatcher(stream_id, flags, category));
+}
+
+testing::Matcher<const nghttp2_frame*> IsRstStream(
+ const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<uint32_t> error_code) {
+ return MakeMatcher(new RstStreamMatcher(stream_id, error_code));
+}
+
+testing::Matcher<const nghttp2_frame*> IsSettings(
+ const testing::Matcher<std::vector<Http2Setting>> values) {
+ return MakeMatcher(new SettingsMatcher(values));
+}
+
+testing::Matcher<const nghttp2_frame*> IsPing(
+ const testing::Matcher<uint64_t> id) {
+ return MakeMatcher(new PingMatcher(id, false));
+}
+
+testing::Matcher<const nghttp2_frame*> IsPingAck(
+ const testing::Matcher<uint64_t> id) {
+ return MakeMatcher(new PingMatcher(id, true));
+}
+
+testing::Matcher<const nghttp2_frame*> IsGoAway(
+ const testing::Matcher<uint32_t> last_stream_id,
+ const testing::Matcher<uint32_t> error_code,
+ const testing::Matcher<absl::string_view> opaque_data) {
+ return MakeMatcher(
+ new GoAwayMatcher(last_stream_id, error_code, opaque_data));
+}
+
+testing::Matcher<const nghttp2_frame*> IsWindowUpdate(
+ const testing::Matcher<uint32_t> delta) {
+ return MakeMatcher(new WindowUpdateMatcher(delta));
+}
+
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.h
new file mode 100644
index 00000000000..9b772ffda63
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.h
@@ -0,0 +1,99 @@
+#ifndef QUICHE_HTTP2_ADAPTER_NGHTTP2_TEST_UTILS_H_
+#define QUICHE_HTTP2_ADAPTER_NGHTTP2_TEST_UTILS_H_
+
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "http2/adapter/http2_protocol.h"
+#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+#include "common/platform/api/quiche_export.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+
+// A simple class that can easily be adapted to act as a nghttp2_data_source.
+class QUICHE_NO_EXPORT TestDataSource {
+ public:
+ explicit TestDataSource(absl::string_view data) : data_(std::string(data)) {}
+
+ absl::string_view ReadNext(size_t size) {
+ const size_t to_send = std::min(size, remaining_.size());
+ auto ret = remaining_.substr(0, to_send);
+ remaining_.remove_prefix(to_send);
+ return ret;
+ }
+
+ size_t SelectPayloadLength(size_t max_length) {
+ return std::min(max_length, remaining_.size());
+ }
+
+ nghttp2_data_provider MakeDataProvider() {
+ return nghttp2_data_provider{
+ .source = {.ptr = this},
+ .read_callback = [](nghttp2_session*, int32_t, uint8_t*, size_t length,
+ uint32_t* data_flags, nghttp2_data_source* source,
+ void*) -> ssize_t {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+ auto* s = static_cast<TestDataSource*>(source->ptr);
+ if (!s->is_data_available()) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ const ssize_t ret = s->SelectPayloadLength(length);
+ if (ret < static_cast<ssize_t>(length)) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+ return ret;
+ }};
+ }
+
+ bool is_data_available() const { return is_data_available_; }
+ void set_is_data_available(bool value) { is_data_available_ = value; }
+
+ private:
+ const std::string data_;
+ absl::string_view remaining_ = data_;
+ bool is_data_available_ = true;
+};
+
+// Matchers for nghttp2 data types.
+testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader(
+ uint32_t streamid, uint8_t type, const testing::Matcher<int> flags);
+testing::Matcher<const nghttp2_frame_hd&> HasFrameHeaderRef(
+ uint32_t streamid, uint8_t type, const testing::Matcher<int> flags);
+
+testing::Matcher<const nghttp2_frame*> IsData(
+ const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<size_t> length, const testing::Matcher<int> flags);
+
+testing::Matcher<const nghttp2_frame*> IsHeaders(
+ const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<int> flags, const testing::Matcher<int> category);
+
+testing::Matcher<const nghttp2_frame*> IsRstStream(
+ const testing::Matcher<uint32_t> stream_id,
+ const testing::Matcher<uint32_t> error_code);
+
+testing::Matcher<const nghttp2_frame*> IsSettings(
+ const testing::Matcher<std::vector<Http2Setting>> values);
+
+testing::Matcher<const nghttp2_frame*> IsPing(
+ const testing::Matcher<uint64_t> id);
+
+testing::Matcher<const nghttp2_frame*> IsPingAck(
+ const testing::Matcher<uint64_t> id);
+
+testing::Matcher<const nghttp2_frame*> IsGoAway(
+ const testing::Matcher<uint32_t> last_stream_id,
+ const testing::Matcher<uint32_t> error_code,
+ const testing::Matcher<absl::string_view> opaque_data);
+
+testing::Matcher<const nghttp2_frame*> IsWindowUpdate(
+ const testing::Matcher<uint32_t> delta);
+
+} // namespace test
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_NGHTTP2_TEST_UTILS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc
index 8d23a55278a..efb811fc35c 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc
@@ -2,10 +2,13 @@
#include <cstdint>
+#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
#include "http2/adapter/http2_protocol.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 {
@@ -28,11 +31,11 @@ void DeleteSession(nghttp2_session* session) {
nghttp2_session_callbacks_unique_ptr MakeCallbacksPtr(
nghttp2_session_callbacks* callbacks) {
- return nghttp2_session_callbacks_unique_ptr(callbacks, DeleteCallbacks);
+ return nghttp2_session_callbacks_unique_ptr(callbacks, &DeleteCallbacks);
}
nghttp2_session_unique_ptr MakeSessionPtr(nghttp2_session* session) {
- return nghttp2_session_unique_ptr(session, DeleteSession);
+ return nghttp2_session_unique_ptr(session, &DeleteSession);
}
uint8_t* ToUint8Ptr(char* str) { return reinterpret_cast<uint8_t*>(str); }
@@ -56,7 +59,8 @@ absl::string_view ToStringView(const uint8_t* pointer, size_t length) {
std::vector<nghttp2_nv> GetNghttp2Nvs(absl::Span<const Header> headers) {
const int num_headers = headers.size();
- auto nghttp2_nvs = std::vector<nghttp2_nv>(num_headers);
+ std::vector<nghttp2_nv> nghttp2_nvs;
+ nghttp2_nvs.reserve(num_headers);
for (int i = 0; i < num_headers; ++i) {
nghttp2_nv header;
uint8_t flags = NGHTTP2_NV_FLAG_NONE;
@@ -85,7 +89,8 @@ std::vector<nghttp2_nv> GetResponseNghttp2Nvs(
absl::string_view response_code) {
// Allocate enough for all headers and also the :status pseudoheader.
const int num_headers = headers.size();
- auto nghttp2_nvs = std::vector<nghttp2_nv>(num_headers + 1);
+ std::vector<nghttp2_nv> nghttp2_nvs;
+ nghttp2_nvs.reserve(num_headers + 1);
// Add the :status pseudoheader first.
nghttp2_nv status;
@@ -97,7 +102,7 @@ std::vector<nghttp2_nv> GetResponseNghttp2Nvs(
nghttp2_nvs.push_back(std::move(status));
// Add the remaining headers.
- for (const auto header_pair : headers) {
+ for (const auto& header_pair : headers) {
nghttp2_nv header;
header.name = ToUint8Ptr(header_pair.first.data());
header.namelen = header_pair.first.size();
@@ -117,5 +122,148 @@ Http2ErrorCode ToHttp2ErrorCode(uint32_t wire_error_code) {
return static_cast<Http2ErrorCode>(wire_error_code);
}
+class Nghttp2DataFrameSource : public DataFrameSource {
+ public:
+ Nghttp2DataFrameSource(nghttp2_data_provider provider,
+ nghttp2_send_data_callback send_data,
+ void* user_data)
+ : provider_(std::move(provider)),
+ send_data_(std::move(send_data)),
+ user_data_(user_data) {}
+
+ std::pair<ssize_t, bool> SelectPayloadLength(size_t max_length) override {
+ const int32_t stream_id = 0;
+ uint32_t data_flags = 0;
+ QUICHE_LOG(INFO) << "Invoking read callback";
+ ssize_t result = provider_.read_callback(
+ nullptr /* session */, stream_id, nullptr /* buf */, max_length,
+ &data_flags, &provider_.source, nullptr /* user_data */);
+ if (result == NGHTTP2_ERR_DEFERRED) {
+ return {kBlocked, false};
+ } else if (result < 0) {
+ return {kError, false};
+ } else if ((data_flags & NGHTTP2_DATA_FLAG_NO_COPY) == 0) {
+ QUICHE_LOG(ERROR) << "Source did not use the zero-copy API!";
+ return {kError, false};
+ } else {
+ if (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM) {
+ send_fin_ = false;
+ }
+ const bool eof = data_flags & NGHTTP2_DATA_FLAG_EOF;
+ return {result, eof};
+ }
+ }
+
+ bool Send(absl::string_view frame_header, size_t payload_length) override {
+ nghttp2_frame frame;
+ frame.hd.type = 0;
+ frame.hd.length = payload_length;
+ frame.hd.flags = 0;
+ frame.hd.stream_id = 0;
+ frame.data.padlen = 0;
+ const int result = send_data_(
+ nullptr /* session */, &frame, ToUint8Ptr(frame_header.data()),
+ payload_length, &provider_.source, user_data_);
+ QUICHE_LOG_IF(ERROR, result < 0 && result != NGHTTP2_ERR_WOULDBLOCK)
+ << "Unexpected error code from send: " << result;
+ return result == 0;
+ }
+
+ bool send_fin() const override { return send_fin_; }
+
+ private:
+ nghttp2_data_provider provider_;
+ nghttp2_send_data_callback send_data_;
+ void* user_data_;
+ bool send_fin_ = true;
+};
+
+std::unique_ptr<DataFrameSource> MakeZeroCopyDataFrameSource(
+ nghttp2_data_provider provider,
+ void* user_data,
+ nghttp2_send_data_callback send_data) {
+ return absl::make_unique<Nghttp2DataFrameSource>(
+ std::move(provider), std::move(send_data), user_data);
+}
+
+absl::string_view ErrorString(uint32_t error_code) {
+ return Http2ErrorCodeToString(static_cast<Http2ErrorCode>(error_code));
+}
+
+size_t PaddingLength(uint8_t flags, size_t padlen) {
+ return (flags & 0x8 ? 1 : 0) + padlen;
+}
+
+struct NvFormatter {
+ void operator()(std::string* out, const nghttp2_nv& nv) {
+ absl::StrAppend(out, ToStringView(nv.name, nv.namelen), ": ",
+ ToStringView(nv.value, nv.valuelen));
+ }
+};
+
+std::string NvsAsString(nghttp2_nv* nva, size_t nvlen) {
+ return absl::StrJoin(absl::MakeConstSpan(nva, nvlen), ", ", NvFormatter());
+}
+
+#define HTTP2_FRAME_SEND_LOG QUICHE_VLOG(1)
+
+void LogBeforeSend(const nghttp2_frame& frame) {
+ switch (static_cast<FrameType>(frame.hd.type)) {
+ case FrameType::DATA:
+ HTTP2_FRAME_SEND_LOG << "Sending DATA on stream " << frame.hd.stream_id
+ << " with length "
+ << frame.hd.length - PaddingLength(frame.hd.flags,
+ frame.data.padlen)
+ << " and padding "
+ << PaddingLength(frame.hd.flags, frame.data.padlen);
+ break;
+ case FrameType::HEADERS:
+ HTTP2_FRAME_SEND_LOG << "Sending HEADERS on stream " << frame.hd.stream_id
+ << " with headers ["
+ << NvsAsString(frame.headers.nva,
+ frame.headers.nvlen)
+ << "]";
+ break;
+ case FrameType::PRIORITY:
+ HTTP2_FRAME_SEND_LOG << "Sending PRIORITY";
+ break;
+ case FrameType::RST_STREAM:
+ HTTP2_FRAME_SEND_LOG << "Sending RST_STREAM on stream "
+ << frame.hd.stream_id << " with error code "
+ << ErrorString(frame.rst_stream.error_code);
+ break;
+ case FrameType::SETTINGS:
+ HTTP2_FRAME_SEND_LOG << "Sending SETTINGS with " << frame.settings.niv
+ << " entries, is_ack: " << (frame.hd.flags & 0x01);
+ break;
+ case FrameType::PUSH_PROMISE:
+ HTTP2_FRAME_SEND_LOG << "Sending PUSH_PROMISE";
+ break;
+ case FrameType::PING: {
+ Http2PingId ping_id;
+ std::memcpy(&ping_id, frame.ping.opaque_data, sizeof(Http2PingId));
+ HTTP2_FRAME_SEND_LOG << "Sending PING with unique_id "
+ << quiche::QuicheEndian::NetToHost64(ping_id)
+ << ", is_ack: " << (frame.hd.flags & 0x01);
+ break;
+ }
+ case FrameType::GOAWAY:
+ HTTP2_FRAME_SEND_LOG << "Sending GOAWAY with last_stream: "
+ << frame.goaway.last_stream_id << " and error "
+ << ErrorString(frame.goaway.error_code);
+ break;
+ case FrameType::WINDOW_UPDATE:
+ HTTP2_FRAME_SEND_LOG << "Sending WINDOW_UPDATE on stream "
+ << frame.hd.stream_id << " with update delta "
+ << frame.window_update.window_size_increment;
+ break;
+ case FrameType::CONTINUATION:
+ HTTP2_FRAME_SEND_LOG << "Sending CONTINUATION, which is unexpected";
+ break;
+ }
+}
+
+#undef HTTP2_FRAME_SEND_LOG
+
} // namespace adapter
} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h
index e2c3e7a37c7..78d4702a3c5 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h
@@ -8,6 +8,7 @@
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
+#include "http2/adapter/data_source.h"
#include "http2/adapter/http2_protocol.h"
#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
#include "spdy/core/spdy_header_block.h"
@@ -20,8 +21,8 @@ inline constexpr int kStreamCallbackFailureStatus =
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
inline constexpr int kCancelStatus = NGHTTP2_ERR_CANCEL;
-using CallbacksDeleter = void (&)(nghttp2_session_callbacks*);
-using SessionDeleter = void (&)(nghttp2_session*);
+using CallbacksDeleter = void (*)(nghttp2_session_callbacks*);
+using SessionDeleter = void (*)(nghttp2_session*);
using nghttp2_session_callbacks_unique_ptr =
std::unique_ptr<nghttp2_session_callbacks, CallbacksDeleter>;
@@ -55,6 +56,16 @@ std::vector<nghttp2_nv> GetResponseNghttp2Nvs(
// based on the RFC 7540 Section 7 suggestion.
Http2ErrorCode ToHttp2ErrorCode(uint32_t wire_error_code);
+// Transforms a nghttp2_data_provider into a DataFrameSource. Assumes that
+// |provider| uses the zero-copy nghttp2_data_source_read_callback API. Unsafe
+// otherwise.
+std::unique_ptr<DataFrameSource> MakeZeroCopyDataFrameSource(
+ nghttp2_data_provider provider,
+ void* user_data,
+ nghttp2_send_data_callback send_data);
+
+void LogBeforeSend(const nghttp2_frame& frame);
+
} // namespace adapter
} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util_test.cc
new file mode 100644
index 00000000000..1ec00ba32ec
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util_test.cc
@@ -0,0 +1,109 @@
+#include "http2/adapter/nghttp2_util.h"
+
+#include "http2/adapter/nghttp2_test_utils.h"
+#include "http2/adapter/test_utils.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+// This send callback assumes |source|'s pointer is a TestDataSource, and
+// |user_data| is a std::string.
+int FakeSendCallback(nghttp2_session*, nghttp2_frame* /*frame*/,
+ const uint8_t* framehd, size_t length,
+ nghttp2_data_source* source, void* user_data) {
+ auto* dest = static_cast<std::string*>(user_data);
+ // Appends the frame header to the string.
+ absl::StrAppend(dest, ToStringView(framehd, 9));
+ auto* test_source = static_cast<TestDataSource*>(source->ptr);
+ absl::string_view payload = test_source->ReadNext(length);
+ // Appends the frame payload to the string.
+ absl::StrAppend(dest, payload);
+ return 0;
+}
+
+TEST(MakeZeroCopyDataFrameSource, EmptyPayload) {
+ std::string result;
+
+ const absl::string_view kEmptyBody = "";
+ TestDataSource body1{kEmptyBody};
+ // The TestDataSource is wrapped in the nghttp2_data_provider data type.
+ nghttp2_data_provider provider = body1.MakeDataProvider();
+
+ // This call transforms it back into a DataFrameSource, which is compatible
+ // with the Http2Adapter API.
+ std::unique_ptr<DataFrameSource> frame_source =
+ MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback);
+ auto [length, eof] = frame_source->SelectPayloadLength(100);
+ EXPECT_EQ(length, 0);
+ EXPECT_TRUE(eof);
+ frame_source->Send("ninebytes", 0);
+ EXPECT_EQ(result, "ninebytes");
+}
+
+TEST(MakeZeroCopyDataFrameSource, ShortPayload) {
+ std::string result;
+
+ const absl::string_view kShortBody =
+ "<html><head><title>Example Page!</title></head>"
+ "<body><div><span><table><tr><th><blink>Wow!!"
+ "</blink></th></tr></table></span></div></body>"
+ "</html>";
+ TestDataSource body1{kShortBody};
+ // The TestDataSource is wrapped in the nghttp2_data_provider data type.
+ nghttp2_data_provider provider = body1.MakeDataProvider();
+
+ // This call transforms it back into a DataFrameSource, which is compatible
+ // with the Http2Adapter API.
+ std::unique_ptr<DataFrameSource> frame_source =
+ MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback);
+ auto [length, eof] = frame_source->SelectPayloadLength(200);
+ EXPECT_EQ(length, kShortBody.size());
+ EXPECT_TRUE(eof);
+ frame_source->Send("ninebytes", length);
+ EXPECT_EQ(result, absl::StrCat("ninebytes", kShortBody));
+}
+
+TEST(MakeZeroCopyDataFrameSource, MultiFramePayload) {
+ std::string result;
+
+ const absl::string_view kShortBody =
+ "<html><head><title>Example Page!</title></head>"
+ "<body><div><span><table><tr><th><blink>Wow!!"
+ "</blink></th></tr></table></span></div></body>"
+ "</html>";
+ TestDataSource body1{kShortBody};
+ // The TestDataSource is wrapped in the nghttp2_data_provider data type.
+ nghttp2_data_provider provider = body1.MakeDataProvider();
+
+ // This call transforms it back into a DataFrameSource, which is compatible
+ // with the Http2Adapter API.
+ std::unique_ptr<DataFrameSource> frame_source =
+ MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback);
+ auto ret = frame_source->SelectPayloadLength(50);
+ EXPECT_EQ(ret.first, 50);
+ EXPECT_FALSE(ret.second);
+ frame_source->Send("ninebyte1", ret.first);
+
+ ret = frame_source->SelectPayloadLength(50);
+ EXPECT_EQ(ret.first, 50);
+ EXPECT_FALSE(ret.second);
+ frame_source->Send("ninebyte2", ret.first);
+
+ ret = frame_source->SelectPayloadLength(50);
+ EXPECT_EQ(ret.first, 44);
+ EXPECT_TRUE(ret.second);
+ frame_source->Send("ninebyte3", ret.first);
+
+ EXPECT_EQ(result,
+ "ninebyte1<html><head><title>Example Page!</title></head><bo"
+ "ninebyte2dy><div><span><table><tr><th><blink>Wow!!</blink><"
+ "ninebyte3/th></tr></table></span></div></body></html>");
+}
+
+} // namespace
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc
index 9f287facb9f..27f011ec62f 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc
@@ -30,6 +30,10 @@ std::unique_ptr<OgHttp2Adapter> OgHttp2Adapter::Create(
OgHttp2Adapter::~OgHttp2Adapter() {}
+bool OgHttp2Adapter::IsServerSession() const {
+ return session_->IsServerSession();
+}
+
ssize_t OgHttp2Adapter::ProcessBytes(absl::string_view bytes) {
return session_->ProcessBytes(bytes);
}
@@ -54,6 +58,10 @@ void OgHttp2Adapter::SubmitPing(Http2PingId ping_id) {
session_->EnqueueFrame(absl::make_unique<SpdyPingIR>(ping_id));
}
+void OgHttp2Adapter::SubmitShutdownNotice() {
+ session_->StartGracefulShutdown();
+}
+
void OgHttp2Adapter::SubmitGoAway(Http2StreamId last_accepted_stream_id,
Http2ErrorCode error_code,
absl::string_view opaque_data) {
@@ -67,18 +75,45 @@ void OgHttp2Adapter::SubmitWindowUpdate(Http2StreamId stream_id,
absl::make_unique<SpdyWindowUpdateIR>(stream_id, window_increment));
}
-void OgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id, bool fin) {
- QUICHE_BUG(oghttp2_submit_metadata) << "Not implemented";
+void OgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id,
+ std::unique_ptr<MetadataSource> source) {
+ session_->SubmitMetadata(stream_id, std::move(source));
}
-std::string OgHttp2Adapter::GetBytesToWrite(absl::optional<size_t> max_bytes) {
- return session_->GetBytesToWrite(max_bytes);
-}
+int OgHttp2Adapter::Send() { return session_->Send(); }
-int OgHttp2Adapter::GetPeerConnectionWindow() const {
+int OgHttp2Adapter::GetSendWindowSize() const {
return session_->GetRemoteWindowSize();
}
+int OgHttp2Adapter::GetStreamSendWindowSize(Http2StreamId stream_id) const {
+ return session_->GetStreamSendWindowSize(stream_id);
+}
+
+int OgHttp2Adapter::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const {
+ return session_->GetStreamReceiveWindowLimit(stream_id);
+}
+
+int OgHttp2Adapter::GetStreamReceiveWindowSize(Http2StreamId stream_id) const {
+ return session_->GetStreamReceiveWindowSize(stream_id);
+}
+
+int OgHttp2Adapter::GetReceiveWindowSize() const {
+ return session_->GetReceiveWindowSize();
+}
+
+int OgHttp2Adapter::GetHpackEncoderDynamicTableSize() const {
+ return session_->GetHpackEncoderDynamicTableSize();
+}
+
+int OgHttp2Adapter::GetHpackDecoderDynamicTableSize() const {
+ return session_->GetHpackDecoderDynamicTableSize();
+}
+
+Http2StreamId OgHttp2Adapter::GetHighestReceivedStreamId() const {
+ return session_->GetHighestReceivedStreamId();
+}
+
void OgHttp2Adapter::MarkDataConsumedForStream(Http2StreamId stream_id,
size_t num_bytes) {
session_->Consume(stream_id, num_bytes);
@@ -90,6 +125,36 @@ void OgHttp2Adapter::SubmitRst(Http2StreamId stream_id,
stream_id, TranslateErrorCode(error_code)));
}
+int32_t OgHttp2Adapter::SubmitRequest(
+ absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source, void* user_data) {
+ return session_->SubmitRequest(headers, std::move(data_source), user_data);
+}
+
+int OgHttp2Adapter::SubmitResponse(
+ Http2StreamId stream_id, absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source) {
+ return session_->SubmitResponse(stream_id, headers, std::move(data_source));
+}
+
+int OgHttp2Adapter::SubmitTrailer(Http2StreamId stream_id,
+ absl::Span<const Header> trailers) {
+ return session_->SubmitTrailer(stream_id, trailers);
+}
+
+void OgHttp2Adapter::SetStreamUserData(Http2StreamId stream_id,
+ void* user_data) {
+ session_->SetStreamUserData(stream_id, user_data);
+}
+
+void* OgHttp2Adapter::GetStreamUserData(Http2StreamId stream_id) {
+ return session_->GetStreamUserData(stream_id);
+}
+
+bool OgHttp2Adapter::ResumeStream(Http2StreamId stream_id) {
+ return session_->ResumeStream(stream_id);
+}
+
const Http2Session& OgHttp2Adapter::session() const {
return *session_;
}
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h
index 572ba04e9fc..9c7bad3c575 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h
@@ -6,11 +6,12 @@
#include "http2/adapter/http2_adapter.h"
#include "http2/adapter/http2_session.h"
#include "http2/adapter/oghttp2_session.h"
+#include "common/platform/api/quiche_export.h"
namespace http2 {
namespace adapter {
-class OgHttp2Adapter : public Http2Adapter {
+class QUICHE_EXPORT_PRIVATE OgHttp2Adapter : public Http2Adapter {
public:
using Options = OgHttp2Session::Options;
static std::unique_ptr<OgHttp2Adapter> Create(Http2VisitorInterface& visitor,
@@ -19,6 +20,7 @@ class OgHttp2Adapter : public Http2Adapter {
~OgHttp2Adapter();
// From Http2Adapter.
+ bool IsServerSession() const override;
ssize_t ProcessBytes(absl::string_view bytes) override;
void SubmitSettings(absl::Span<const Http2Setting> settings) override;
void SubmitPriorityForStream(Http2StreamId stream_id,
@@ -26,17 +28,38 @@ class OgHttp2Adapter : public Http2Adapter {
int weight,
bool exclusive) override;
void SubmitPing(Http2PingId ping_id) override;
+ void SubmitShutdownNotice() override;
void SubmitGoAway(Http2StreamId last_accepted_stream_id,
Http2ErrorCode error_code,
absl::string_view opaque_data) override;
void SubmitWindowUpdate(Http2StreamId stream_id,
int window_increment) override;
- void SubmitMetadata(Http2StreamId stream_id, bool fin) override;
- std::string GetBytesToWrite(absl::optional<size_t> max_bytes) override;
- int GetPeerConnectionWindow() const override;
+ void SubmitMetadata(Http2StreamId stream_id,
+ std::unique_ptr<MetadataSource> source) override;
+ int Send() override;
+ int GetSendWindowSize() const override;
+ int GetStreamSendWindowSize(Http2StreamId stream_id) const override;
+ int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const override;
+ int GetStreamReceiveWindowSize(Http2StreamId stream_id) const override;
+ int GetReceiveWindowSize() const override;
+ int GetHpackEncoderDynamicTableSize() const override;
+ int GetHpackDecoderDynamicTableSize() const override;
+ Http2StreamId GetHighestReceivedStreamId() const override;
void MarkDataConsumedForStream(Http2StreamId stream_id,
size_t num_bytes) override;
void SubmitRst(Http2StreamId stream_id, Http2ErrorCode error_code) override;
+ int32_t SubmitRequest(absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source,
+ void* user_data) override;
+ int SubmitResponse(Http2StreamId stream_id, absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source) override;
+
+ int SubmitTrailer(Http2StreamId stream_id,
+ absl::Span<const Header> trailers) override;
+
+ void SetStreamUserData(Http2StreamId stream_id, void* user_data) override;
+ void* GetStreamUserData(Http2StreamId stream_id) override;
+ bool ResumeStream(Http2StreamId stream_id) override;
const Http2Session& session() const;
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc
index 4c849f6fd6d..03b45df6aa9 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc
@@ -1,6 +1,8 @@
#include "http2/adapter/oghttp2_adapter.h"
+#include "absl/strings/str_join.h"
#include "http2/adapter/mock_http2_visitor.h"
+#include "http2/adapter/oghttp2_util.h"
#include "http2/adapter/test_frame_sequence.h"
#include "http2/adapter/test_utils.h"
#include "common/platform/api/quiche_test.h"
@@ -11,6 +13,23 @@ namespace adapter {
namespace test {
namespace {
+using testing::_;
+
+enum FrameType {
+ DATA,
+ HEADERS,
+ PRIORITY,
+ RST_STREAM,
+ SETTINGS,
+ PUSH_PROMISE,
+ PING,
+ GOAWAY,
+ WINDOW_UPDATE,
+ CONTINUATION,
+};
+
+using spdy::SpdyFrameType;
+
class OgHttp2AdapterTest : public testing::Test {
protected:
void SetUp() override {
@@ -18,10 +37,14 @@ class OgHttp2AdapterTest : public testing::Test {
adapter_ = OgHttp2Adapter::Create(http2_visitor_, options);
}
- testing::StrictMock<MockHttp2Visitor> http2_visitor_;
+ DataSavingVisitor http2_visitor_;
std::unique_ptr<OgHttp2Adapter> adapter_;
};
+TEST_F(OgHttp2AdapterTest, IsServerSession) {
+ EXPECT_TRUE(adapter_->IsServerSession());
+}
+
TEST_F(OgHttp2AdapterTest, ProcessBytes) {
testing::InSequence seq;
EXPECT_CALL(http2_visitor_, OnFrameHeader(0, 0, 4, 0));
@@ -33,13 +56,527 @@ TEST_F(OgHttp2AdapterTest, ProcessBytes) {
TestFrameSequence().ClientPreface().Ping(17).Serialize());
}
+TEST(OgHttp2AdapterClientTest, ClientHandlesTrailers) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Headers(1, {{"final-status", "A-OK"}},
+ /*fin=*/true)
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ 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(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, "final-status", "A-OK"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnEndStream(1));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ const size_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(OgHttp2AdapterClientTest, ClientHandlesMetadata) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .Metadata(0, "Example connection metadata")
+ .Headers(1,
+ {{":status", "200"},
+ {"server", "my-fake-server"},
+ {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}},
+ /*fin=*/false)
+ .Metadata(1, "Example stream metadata")
+ .Data(1, "This is the response body.", true)
+ .Serialize();
+
+ // 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, _, kMetadataFrameType, 4));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataEndForStream(0));
+ 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, _, kMetadataFrameType, 4));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataEndForStream(1));
+ EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 1));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, 26));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body."));
+ EXPECT_CALL(visitor, OnEndStream(1));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ const size_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(OgHttp2AdapterClientTest, ClientRstStreamWhileHandlingHeaders) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ 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"))
+ .WillOnce(testing::DoAll(
+ testing::InvokeWithoutArgs([&adapter]() {
+ adapter->SubmitRst(1, Http2ErrorCode::REFUSED_STREAM);
+ }),
+ testing::Return(Http2VisitorInterface::HEADER_RST_STREAM)));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, _));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body."));
+
+ const size_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, stream_id1, 4, 0x0,
+ static_cast<int>(Http2ErrorCode::REFUSED_STREAM)));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM}));
+}
+
+TEST(OgHttp2AdapterClientTest, ClientConnectionErrorWhileHandlingHeaders) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ 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"))
+ .WillOnce(
+ testing::Return(Http2VisitorInterface::HEADER_CONNECTION_ERROR));
+ EXPECT_CALL(visitor, OnConnectionError());
+ // Note: OgHttp2Adapter continues processing bytes until the input is
+ // complete.
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, _));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body."));
+
+ const size_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+TEST(OgHttp2AdapterClientTest, ClientRejectsHeaders) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1))
+ .WillOnce(testing::Return(false));
+ // Rejecting headers leads to a connection error.
+ EXPECT_CALL(visitor, OnConnectionError());
+ // Note: OgHttp2Adapter continues processing bytes until the input is
+ // complete.
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, _));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body."));
+
+ const size_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_result, stream_frames.size());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
+// TODO(birenroy): Validate headers and re-enable this test. The library should
+// invoke OnErrorDebug() with an error message for the invalid header. The
+// library should also invoke OnInvalidFrame() for the invalid HEADERS frame.
+TEST(OgHttp2AdapterClientTest, DISABLED_ClientHandlesInvalidTrailers) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ testing::InSequence s;
+
+ const std::vector<const Header> headers1 =
+ ToHeaders({{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}});
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ const int32_t stream_id1 =
+ adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1));
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
+ int result = adapter->Send();
+ EXPECT_EQ(0, result);
+ absl::string_view data = visitor.data();
+ EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
+
+ const std::string stream_frames =
+ TestFrameSequence()
+ .ServerPreface()
+ .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.")
+ .Headers(1, {{":bad-status", "9000"}},
+ /*fin=*/true)
+ .Serialize();
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ 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(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+
+ // Bad status trailer will cause a PROTOCOL_ERROR. The header is never
+ // delivered in an OnHeaderForStream callback.
+
+ const size_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, 1));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR));
+
+ EXPECT_TRUE(adapter->session().want_write());
+ result = adapter->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM}));
+}
+
TEST_F(OgHttp2AdapterTest, SubmitMetadata) {
- EXPECT_QUICHE_BUG(adapter_->SubmitMetadata(3, true), "Not implemented");
+ auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders(
+ {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}})));
+ adapter_->SubmitMetadata(1, std::move(source));
+ EXPECT_TRUE(adapter_->session().want_write());
+
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0));
+
+ int result = adapter_->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(
+ http2_visitor_.data(),
+ EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ static_cast<spdy::SpdyFrameType>(kMetadataFrameType)}));
+ EXPECT_FALSE(adapter_->session().want_write());
+}
+
+TEST_F(OgHttp2AdapterTest, SubmitConnectionMetadata) {
+ auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders(
+ {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}})));
+ adapter_->SubmitMetadata(0, std::move(source));
+ EXPECT_TRUE(adapter_->session().want_write());
+
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(kMetadataFrameType, 0, _, 0x4));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(kMetadataFrameType, 0, _, 0x4, 0));
+
+ int result = adapter_->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(
+ http2_visitor_.data(),
+ EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ static_cast<spdy::SpdyFrameType>(kMetadataFrameType)}));
+ EXPECT_FALSE(adapter_->session().want_write());
}
-TEST_F(OgHttp2AdapterTest, GetPeerConnectionWindow) {
- const int peer_window = adapter_->GetPeerConnectionWindow();
- EXPECT_GT(peer_window, 0);
+TEST_F(OgHttp2AdapterTest, GetSendWindowSize) {
+ const int peer_window = adapter_->GetSendWindowSize();
+ EXPECT_EQ(peer_window, kInitialFlowControlWindowSize);
}
TEST_F(OgHttp2AdapterTest, MarkDataConsumedForStream) {
@@ -62,12 +599,27 @@ TEST_F(OgHttp2AdapterTest, TestSerialize) {
adapter_->SubmitWindowUpdate(3, 127);
EXPECT_TRUE(adapter_->session().want_write());
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PRIORITY, 3, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(PRIORITY, 3, _, 0x0, 0));
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(RST_STREAM, 3, _, 0x0, 0x8));
+ EXPECT_CALL(http2_visitor_, OnCloseStream(3, Http2ErrorCode::NO_ERROR));
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PING, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(PING, 0, _, 0x0, 0));
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(WINDOW_UPDATE, 3, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(WINDOW_UPDATE, 3, _, 0x0, 0));
+
+ int result = adapter_->Send();
+ EXPECT_EQ(0, result);
EXPECT_THAT(
- adapter_->GetBytesToWrite(absl::nullopt),
- EqualsFrames(
- {spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::PRIORITY,
- spdy::SpdyFrameType::RST_STREAM, spdy::SpdyFrameType::PING,
- spdy::SpdyFrameType::GOAWAY, spdy::SpdyFrameType::WINDOW_UPDATE}));
+ http2_visitor_.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PRIORITY,
+ SpdyFrameType::RST_STREAM, SpdyFrameType::PING,
+ SpdyFrameType::GOAWAY, SpdyFrameType::WINDOW_UPDATE}));
EXPECT_FALSE(adapter_->session().want_write());
}
@@ -80,14 +632,271 @@ TEST_F(OgHttp2AdapterTest, TestPartialSerialize) {
adapter_->SubmitPing(42);
EXPECT_TRUE(adapter_->session().want_write());
- const std::string first_part = adapter_->GetBytesToWrite(10);
+ http2_visitor_.set_send_limit(20);
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ int result = adapter_->Send();
+ EXPECT_EQ(0, result);
+ EXPECT_TRUE(adapter_->session().want_write());
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+ result = adapter_->Send();
+ EXPECT_EQ(0, result);
EXPECT_TRUE(adapter_->session().want_write());
- const std::string second_part = adapter_->GetBytesToWrite(absl::nullopt);
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PING, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(PING, 0, _, 0x0, 0));
+ result = adapter_->Send();
+ EXPECT_EQ(0, result);
EXPECT_FALSE(adapter_->session().want_write());
- EXPECT_THAT(
- absl::StrCat(first_part, second_part),
- EqualsFrames({spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::GOAWAY,
- spdy::SpdyFrameType::PING}));
+ EXPECT_THAT(http2_visitor_.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY,
+ SpdyFrameType::PING}));
+}
+
+TEST(OgHttp2AdapterServerTest, ClientSendsContinuation) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+ EXPECT_FALSE(adapter->session().want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true,
+ /*add_continuation=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 1));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https"));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4));
+ 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, OnEndStream(1));
+
+ const size_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+}
+
+TEST(OgHttp2AdapterServerTest, ClientSendsMetadataWithContinuation) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+ EXPECT_FALSE(adapter->session().want_write());
+
+ const std::string frames =
+ TestFrameSequence()
+ .ClientPreface()
+ .Metadata(0, "Example connection metadata in multiple frames", true)
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/false,
+ /*add_continuation=*/true)
+ .Metadata(1,
+ "Some stream metadata that's also sent in multiple frames",
+ true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Metadata on stream 0
+ EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 0));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(0, _));
+ EXPECT_CALL(visitor, OnMetadataEndForStream(0));
+
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https"));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ // Metadata on stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 0));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4));
+ EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataForStream(1, _));
+ EXPECT_CALL(visitor, OnMetadataEndForStream(1));
+
+ const size_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+ EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload(
+ "Example connection metadata in multiple frames"),
+ absl::StrJoin(visitor.GetMetadata(0), ""));
+ EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload(
+ "Some stream metadata that's also sent in multiple frames"),
+ absl::StrJoin(visitor.GetMetadata(1), ""));
+}
+
+TEST(OgHttp2AdapterServerTest, ServerSendsInvalidTrailers) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+ EXPECT_FALSE(adapter->session().want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ 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, OnEndStream(1));
+
+ const size_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ const absl::string_view kBody = "This is an example response body.";
+
+ // The body source must indicate that the end of the body is not the end of
+ // the stream.
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false);
+ body1->AppendPayload(kBody);
+ body1->EndData();
+ int submit_result = adapter->SubmitResponse(
+ 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}),
+ std::move(body1));
+ EXPECT_EQ(submit_result, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames(
+ {spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::HEADERS, spdy::SpdyFrameType::DATA}));
+ EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody));
+ visitor.Clear();
+ EXPECT_FALSE(adapter->session().want_write());
+
+ // The body source has been exhausted by the call to Send() above.
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+ int trailer_result =
+ adapter->SubmitTrailer(1, ToHeaders({{":final-status", "a-ok"}}));
+ ASSERT_EQ(trailer_result, 0);
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+
+ send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+}
+
+TEST(OgHttp2AdapterServerTest, ServerErrorWhileHandlingHeaders) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"},
+ {"accept", "some bogus value!"}},
+ /*fin=*/false)
+ .WindowUpdate(1, 2000)
+ .Data(1, "This is the request body.")
+ .WindowUpdate(0, 2000)
+ .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(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, OnHeaderForStream(1, "accept", "some bogus value!"))
+ .WillOnce(testing::Return(Http2VisitorInterface::HEADER_RST_STREAM));
+ EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0));
+ EXPECT_CALL(visitor, OnWindowUpdate(1, 2000));
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(1, _));
+ EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body."));
+ EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0));
+ EXPECT_CALL(visitor, OnWindowUpdate(0, 2000));
+
+ const size_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ EXPECT_TRUE(adapter->session().want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, 1, 4, 0x0,
+ static_cast<int>(Http2ErrorCode::INTERNAL_ERROR)));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ int send_result = adapter->Send();
+ // Some bytes should have been serialized.
+ EXPECT_EQ(0, send_result);
+ // SETTINGS ack
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM}));
}
} // namespace
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc
index 2222fed4b6b..3a32b2e70ea 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc
@@ -1,29 +1,150 @@
#include "http2/adapter/oghttp2_session.h"
+#include <tuple>
+
#include "absl/strings/escaping.h"
+#include "http2/adapter/oghttp2_util.h"
namespace http2 {
namespace adapter {
+namespace {
+
+const size_t kMaxMetadataFrameSize = 16384;
+
+// TODO(birenroy): Consider incorporating spdy::FlagsSerializionVisitor here.
+class FrameAttributeCollector : public spdy::SpdyFrameVisitor {
+ public:
+ FrameAttributeCollector() = default;
+ void VisitData(const spdy::SpdyDataIR& data) override {
+ frame_type_ = static_cast<uint8_t>(data.frame_type());
+ stream_id_ = data.stream_id();
+ length_ =
+ data.data_len() + (data.padded() ? 1 : 0) + data.padding_payload_len();
+ flags_ = (data.fin() ? 0x1 : 0) | (data.padded() ? 0x8 : 0);
+ }
+ void VisitHeaders(const spdy::SpdyHeadersIR& headers) override {
+ frame_type_ = static_cast<uint8_t>(headers.frame_type());
+ stream_id_ = headers.stream_id();
+ length_ = headers.size() - spdy::kFrameHeaderSize;
+ flags_ = 0x4 | (headers.fin() ? 0x1 : 0) | (headers.padded() ? 0x8 : 0) |
+ (headers.has_priority() ? 0x20 : 0);
+ }
+ void VisitPriority(const spdy::SpdyPriorityIR& priority) override {
+ frame_type_ = static_cast<uint8_t>(priority.frame_type());
+ frame_type_ = 2;
+ length_ = 5;
+ stream_id_ = priority.stream_id();
+ }
+ void VisitRstStream(const spdy::SpdyRstStreamIR& rst_stream) override {
+ frame_type_ = static_cast<uint8_t>(rst_stream.frame_type());
+ frame_type_ = 3;
+ length_ = 4;
+ stream_id_ = rst_stream.stream_id();
+ error_code_ = rst_stream.error_code();
+ }
+ void VisitSettings(const spdy::SpdySettingsIR& settings) override {
+ frame_type_ = static_cast<uint8_t>(settings.frame_type());
+ frame_type_ = 4;
+ length_ = 6 * settings.values().size();
+ flags_ = (settings.is_ack() ? 0x1 : 0);
+ }
+ void VisitPushPromise(const spdy::SpdyPushPromiseIR& push_promise) override {
+ frame_type_ = static_cast<uint8_t>(push_promise.frame_type());
+ frame_type_ = 5;
+ length_ = push_promise.size() - spdy::kFrameHeaderSize;
+ stream_id_ = push_promise.stream_id();
+ flags_ = (push_promise.padded() ? 0x8 : 0);
+ }
+ void VisitPing(const spdy::SpdyPingIR& ping) override {
+ frame_type_ = static_cast<uint8_t>(ping.frame_type());
+ frame_type_ = 6;
+ length_ = 8;
+ flags_ = (ping.is_ack() ? 0x1 : 0);
+ }
+ void VisitGoAway(const spdy::SpdyGoAwayIR& goaway) override {
+ frame_type_ = static_cast<uint8_t>(goaway.frame_type());
+ frame_type_ = 7;
+ length_ = goaway.size() - spdy::kFrameHeaderSize;
+ error_code_ = goaway.error_code();
+ }
+ void VisitWindowUpdate(
+ const spdy::SpdyWindowUpdateIR& window_update) override {
+ frame_type_ = static_cast<uint8_t>(window_update.frame_type());
+ frame_type_ = 8;
+ length_ = 4;
+ stream_id_ = window_update.stream_id();
+ }
+ void VisitContinuation(
+ const spdy::SpdyContinuationIR& continuation) override {
+ frame_type_ = static_cast<uint8_t>(continuation.frame_type());
+ stream_id_ = continuation.stream_id();
+ flags_ = continuation.end_headers() ? 0x4 : 0;
+ length_ = continuation.size() - spdy::kFrameHeaderSize;
+ }
+ void VisitUnknown(const spdy::SpdyUnknownIR& unknown) override {
+ frame_type_ = static_cast<uint8_t>(unknown.frame_type());
+ stream_id_ = unknown.stream_id();
+ flags_ = unknown.flags();
+ length_ = unknown.size() - spdy::kFrameHeaderSize;
+ }
+ void VisitAltSvc(const spdy::SpdyAltSvcIR& /*altsvc*/) override {}
+ void VisitPriorityUpdate(
+ const spdy::SpdyPriorityUpdateIR& /*priority_update*/) override {}
+ void VisitAcceptCh(const spdy::SpdyAcceptChIR& /*accept_ch*/) override {}
+
+ uint32_t stream_id() { return stream_id_; }
+ uint32_t length() { return length_; }
+ uint32_t error_code() { return error_code_; }
+ uint8_t frame_type() { return frame_type_; }
+ uint8_t flags() { return flags_; }
+
+ private:
+ uint32_t stream_id_ = 0;
+ uint32_t length_ = 0;
+ uint32_t error_code_ = 0;
+ uint8_t frame_type_ = 0;
+ uint8_t flags_ = 0;
+};
+
+} // namespace
+
void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockStart() {
- visitor_.OnBeginHeadersForStream(stream_id_);
+ const bool status = visitor_.OnBeginHeadersForStream(stream_id_);
+ if (!status) {
+ result_ = Http2VisitorInterface::HEADER_CONNECTION_ERROR;
+ }
}
void OgHttp2Session::PassthroughHeadersHandler::OnHeader(
absl::string_view key,
absl::string_view value) {
- visitor_.OnHeaderForStream(stream_id_, key, value);
+ if (result_ == Http2VisitorInterface::HEADER_OK) {
+ result_ = visitor_.OnHeaderForStream(stream_id_, key, value);
+ }
}
void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockEnd(
size_t /* uncompressed_header_bytes */,
size_t /* compressed_header_bytes */) {
- visitor_.OnEndHeadersForStream(stream_id_);
+ if (result_ == Http2VisitorInterface::HEADER_OK) {
+ visitor_.OnEndHeadersForStream(stream_id_);
+ } else {
+ session_.OnHeaderStatus(stream_id_, result_);
+ }
}
OgHttp2Session::OgHttp2Session(Http2VisitorInterface& visitor, Options options)
- : visitor_(visitor), headers_handler_(visitor), options_(options) {
+ : visitor_(visitor),
+ headers_handler_(*this, visitor),
+ connection_window_manager_(kInitialFlowControlWindowSize,
+ [this](size_t window_update_delta) {
+ SendWindowUpdate(kConnectionStreamId,
+ window_update_delta);
+ }),
+ options_(options) {
decoder_.set_visitor(this);
+ decoder_.set_extension_visitor(this);
if (options_.perspective == Perspective::kServer) {
remaining_preface_ = {spdy::kHttp2ConnectionHeaderPrefix,
spdy::kHttp2ConnectionHeaderPrefixSize};
@@ -32,6 +153,70 @@ OgHttp2Session::OgHttp2Session(Http2VisitorInterface& visitor, Options options)
OgHttp2Session::~OgHttp2Session() {}
+void OgHttp2Session::SetStreamUserData(Http2StreamId stream_id,
+ void* user_data) {
+ auto it = stream_map_.find(stream_id);
+ if (it != stream_map_.end()) {
+ it->second.user_data = user_data;
+ }
+}
+
+void* OgHttp2Session::GetStreamUserData(Http2StreamId stream_id) {
+ auto it = stream_map_.find(stream_id);
+ if (it != stream_map_.end()) {
+ return it->second.user_data;
+ }
+ return nullptr;
+}
+
+bool OgHttp2Session::ResumeStream(Http2StreamId stream_id) {
+ auto it = stream_map_.find(stream_id);
+ if (it->second.outbound_body == nullptr ||
+ !write_scheduler_.StreamRegistered(stream_id)) {
+ return false;
+ }
+ write_scheduler_.MarkStreamReady(stream_id, /*add_to_front=*/false);
+ return true;
+}
+
+int OgHttp2Session::GetStreamSendWindowSize(Http2StreamId stream_id) const {
+ auto it = stream_map_.find(stream_id);
+ if (it != stream_map_.end()) {
+ return it->second.send_window;
+ }
+ return -1;
+}
+
+int OgHttp2Session::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const {
+ auto it = stream_map_.find(stream_id);
+ if (it != stream_map_.end()) {
+ return it->second.window_manager.WindowSizeLimit();
+ }
+ return -1;
+}
+
+int OgHttp2Session::GetStreamReceiveWindowSize(Http2StreamId stream_id) const {
+ auto it = stream_map_.find(stream_id);
+ if (it != stream_map_.end()) {
+ return it->second.window_manager.CurrentWindowSize();
+ }
+ return -1;
+}
+
+int OgHttp2Session::GetReceiveWindowSize() const {
+ return connection_window_manager_.CurrentWindowSize();
+}
+
+int OgHttp2Session::GetHpackEncoderDynamicTableSize() const {
+ const spdy::HpackEncoder* encoder = framer_.GetHpackEncoder();
+ return encoder == nullptr ? 0 : encoder->GetDynamicTableSize();
+}
+
+int OgHttp2Session::GetHpackDecoderDynamicTableSize() const {
+ const spdy::HpackDecoderAdapter* decoder = decoder_.GetHpackDecoder();
+ return decoder == nullptr ? 0 : decoder->GetDynamicTableSize();
+}
+
ssize_t OgHttp2Session::ProcessBytes(absl::string_view bytes) {
ssize_t preface_consumed = 0;
if (!remaining_preface_.empty()) {
@@ -68,27 +253,317 @@ int OgHttp2Session::Consume(Http2StreamId stream_id, size_t num_bytes) {
} else {
it->second.window_manager.MarkDataFlushed(num_bytes);
}
+ connection_window_manager_.MarkDataFlushed(num_bytes);
return 0; // Remove?
}
+void OgHttp2Session::StartGracefulShutdown() {
+ if (options_.perspective == Perspective::kServer) {
+ if (!queued_goaway_) {
+ EnqueueFrame(absl::make_unique<spdy::SpdyGoAwayIR>(
+ std::numeric_limits<int32_t>::max(), spdy::ERROR_CODE_NO_ERROR,
+ "graceful_shutdown"));
+ }
+ } else {
+ QUICHE_LOG(ERROR) << "Graceful shutdown not needed for clients.";
+ }
+}
+
void OgHttp2Session::EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame) {
+ if (frame->frame_type() == spdy::SpdyFrameType::GOAWAY) {
+ queued_goaway_ = true;
+ } else if (frame->frame_type() == spdy::SpdyFrameType::RST_STREAM) {
+ streams_reset_.insert(frame->stream_id());
+ auto iter = stream_map_.find(frame->stream_id());
+ if (iter != stream_map_.end()) {
+ iter->second.half_closed_local = true;
+ }
+ }
frames_.push_back(std::move(frame));
}
-std::string OgHttp2Session::GetBytesToWrite(absl::optional<size_t> max_bytes) {
- const size_t serialized_max =
- max_bytes ? max_bytes.value() : std::numeric_limits<size_t>::max();
- std::string serialized = std::move(serialized_prefix_);
- while (serialized.size() < serialized_max && !frames_.empty()) {
- spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frames_.front());
- absl::StrAppend(&serialized, absl::string_view(frame));
- frames_.pop_front();
+int OgHttp2Session::Send() {
+ MaybeSetupPreface();
+ ssize_t result = std::numeric_limits<ssize_t>::max();
+ // Flush any serialized prefix.
+ while (result > 0 && !serialized_prefix_.empty()) {
+ result = visitor_.OnReadyToSend(serialized_prefix_);
+ if (result > 0) {
+ serialized_prefix_.erase(0, result);
+ }
+ }
+ if (!serialized_prefix_.empty()) {
+ return result < 0 ? result : 0;
+ }
+ bool continue_writing = SendQueuedFrames();
+ while (continue_writing && !connection_metadata_.empty()) {
+ continue_writing = SendMetadata(0, connection_metadata_);
+ }
+ // Wake streams for writes.
+ while (continue_writing && write_scheduler_.HasReadyStreams() &&
+ connection_send_window_ > 0) {
+ const Http2StreamId stream_id = write_scheduler_.PopNextReadyStream();
+ // TODO(birenroy): Add a return value to indicate write blockage, so streams
+ // aren't woken unnecessarily.
+ continue_writing = WriteForStream(stream_id);
+ }
+ if (continue_writing) {
+ SendQueuedFrames();
+ }
+ return 0;
+}
+
+bool OgHttp2Session::SendQueuedFrames() {
+ // Serialize and send frames in the queue.
+ while (!frames_.empty()) {
+ const auto& frame_ptr = frames_.front();
+ FrameAttributeCollector c;
+ frame_ptr->Visit(&c);
+ visitor_.OnBeforeFrameSent(c.frame_type(), c.stream_id(), c.length(),
+ c.flags());
+ spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frame_ptr);
+ const ssize_t result = visitor_.OnReadyToSend(absl::string_view(frame));
+ if (result < 0) {
+ visitor_.OnConnectionError();
+ return false;
+ } else if (result == 0) {
+ // Write blocked.
+ return false;
+ } else {
+ visitor_.OnFrameSent(c.frame_type(), c.stream_id(), c.length(), c.flags(),
+ c.error_code());
+ if (static_cast<FrameType>(c.frame_type()) == FrameType::RST_STREAM) {
+ // If this endpoint is resetting the stream, the stream should be
+ // closed. This endpoint is already aware of the outbound RST_STREAM and
+ // its error code, so close with NO_ERROR.
+ visitor_.OnCloseStream(c.stream_id(), Http2ErrorCode::NO_ERROR);
+ }
+
+ frames_.pop_front();
+ if (static_cast<size_t>(result) < frame.size()) {
+ // The frame was partially written, so the rest must be buffered.
+ serialized_prefix_.assign(frame.data() + result, frame.size() - result);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool OgHttp2Session::WriteForStream(Http2StreamId stream_id) {
+ auto it = stream_map_.find(stream_id);
+ if (it == stream_map_.end()) {
+ QUICHE_LOG(ERROR) << "Can't find stream " << stream_id
+ << " which is ready to write!";
+ return true;
+ }
+ StreamState& state = it->second;
+ bool connection_can_write = true;
+ if (!state.outbound_metadata.empty()) {
+ connection_can_write = SendMetadata(stream_id, state.outbound_metadata);
+ }
+
+ if (state.outbound_body == nullptr) {
+ // No data to send, but there might be trailers.
+ if (state.trailers != nullptr) {
+ auto block_ptr = std::move(state.trailers);
+ if (state.half_closed_local) {
+ QUICHE_LOG(ERROR) << "Sent fin; can't send trailers.";
+ } else {
+ SendTrailers(stream_id, std::move(*block_ptr));
+ MaybeCloseWithRstStream(stream_id, state);
+ }
+ }
+ return true;
+ }
+ bool source_can_produce = true;
+ int32_t available_window = std::min(
+ std::min(connection_send_window_, state.send_window), max_frame_payload_);
+ while (connection_can_write && available_window > 0 &&
+ state.outbound_body != nullptr) {
+ ssize_t length;
+ bool end_data;
+ std::tie(length, end_data) =
+ state.outbound_body->SelectPayloadLength(available_window);
+ if (length == 0 && !end_data) {
+ source_can_produce = false;
+ break;
+ } else if (length == DataFrameSource::kError) {
+ source_can_produce = false;
+ visitor_.OnCloseStream(stream_id, Http2ErrorCode::INTERNAL_ERROR);
+ break;
+ }
+ const bool fin = end_data ? state.outbound_body->send_fin() : false;
+ if (length > 0 || fin) {
+ spdy::SpdyDataIR data(stream_id);
+ data.set_fin(fin);
+ data.SetDataShallow(length);
+ spdy::SpdySerializedFrame header =
+ spdy::SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField(
+ data);
+ QUICHE_DCHECK(serialized_prefix_.empty() && frames_.empty());
+ const bool success =
+ state.outbound_body->Send(absl::string_view(header), length);
+ if (!success) {
+ connection_can_write = false;
+ break;
+ }
+ visitor_.OnFrameSent(/* DATA */ 0, stream_id, length, fin ? 0x1 : 0x0, 0);
+ connection_send_window_ -= length;
+ state.send_window -= length;
+ available_window =
+ std::min(std::min(connection_send_window_, state.send_window),
+ max_frame_payload_);
+ }
+ if (end_data) {
+ bool sent_trailers = false;
+ if (state.trailers != nullptr) {
+ auto block_ptr = std::move(state.trailers);
+ if (fin) {
+ QUICHE_LOG(ERROR) << "Sent fin; can't send trailers.";
+ } else {
+ SendTrailers(stream_id, std::move(*block_ptr));
+ sent_trailers = true;
+ }
+ }
+ state.outbound_body = nullptr;
+ if (fin || sent_trailers) {
+ MaybeCloseWithRstStream(stream_id, state);
+ }
+ }
+ }
+ // If the stream still has data to send, it should be marked as ready in the
+ // write scheduler.
+ if (source_can_produce && state.send_window > 0 &&
+ state.outbound_body != nullptr) {
+ write_scheduler_.MarkStreamReady(stream_id, false);
+ }
+ // Streams can continue writing as long as the connection is not write-blocked
+ // and there is additional flow control quota available.
+ return connection_can_write && available_window > 0;
+}
+
+bool OgHttp2Session::SendMetadata(Http2StreamId stream_id,
+ OgHttp2Session::MetadataSequence& sequence) {
+ auto payload_buffer = absl::make_unique<uint8_t[]>(kMaxMetadataFrameSize);
+ while (!sequence.empty()) {
+ MetadataSource& source = *sequence.front();
+
+ ssize_t written;
+ bool end_metadata;
+ std::tie(written, end_metadata) =
+ source.Pack(payload_buffer.get(), kMaxMetadataFrameSize);
+ if (written < 0) {
+ // Did not touch the connection, so perhaps writes are still possible.
+ return true;
+ }
+ QUICHE_DCHECK_LE(static_cast<size_t>(written), kMaxMetadataFrameSize);
+ auto payload = absl::string_view(
+ reinterpret_cast<const char*>(payload_buffer.get()), written);
+ EnqueueFrame(absl::make_unique<spdy::SpdyUnknownIR>(
+ stream_id, kMetadataFrameType, end_metadata ? kMetadataEndFlag : 0u,
+ std::string(payload)));
+ if (end_metadata) {
+ sequence.erase(sequence.begin());
+ }
+ }
+ return SendQueuedFrames();
+}
+
+int32_t OgHttp2Session::SubmitRequest(
+ absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source, void* user_data) {
+ // TODO(birenroy): return an error for the incorrect perspective
+ const Http2StreamId stream_id = next_stream_id_;
+ next_stream_id_ += 2;
+ // Convert headers to header block, create headers frame.
+ auto frame =
+ absl::make_unique<spdy::SpdyHeadersIR>(stream_id, ToHeaderBlock(headers));
+ // Add data source and user data to stream state
+ auto iter = CreateStream(stream_id);
+ write_scheduler_.MarkStreamReady(stream_id, false);
+ if (data_source == nullptr) {
+ frame->set_fin(true);
+ iter->second.half_closed_local = true;
+ } else {
+ iter->second.outbound_body = std::move(data_source);
+ }
+ iter->second.user_data = user_data;
+ // Enqueue headers frame
+ EnqueueFrame(std::move(frame));
+ return stream_id;
+}
+
+int OgHttp2Session::SubmitResponse(
+ Http2StreamId stream_id, absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source) {
+ // TODO(birenroy): return an error for the incorrect perspective
+ auto iter = stream_map_.find(stream_id);
+ if (iter == stream_map_.end()) {
+ QUICHE_LOG(ERROR) << "Unable to find stream " << stream_id;
+ return -501; // NGHTTP2_ERR_INVALID_ARGUMENT
+ }
+ // Convert headers to header block, create headers frame
+ auto frame =
+ absl::make_unique<spdy::SpdyHeadersIR>(stream_id, ToHeaderBlock(headers));
+ if (data_source == nullptr) {
+ frame->set_fin(true);
+ if (iter->second.half_closed_remote) {
+ visitor_.OnCloseStream(stream_id, Http2ErrorCode::NO_ERROR);
+ }
+ // TODO(birenroy): the server adapter should probably delete stream state
+ // when calling visitor_.OnCloseStream.
+ } else {
+ // Add data source to stream state
+ iter->second.outbound_body = std::move(data_source);
+ write_scheduler_.MarkStreamReady(stream_id, false);
+ }
+ EnqueueFrame(std::move(frame));
+ return 0;
+}
+
+int OgHttp2Session::SubmitTrailer(Http2StreamId stream_id,
+ absl::Span<const Header> trailers) {
+ // TODO(birenroy): Reject trailers when acting as a client?
+ auto iter = stream_map_.find(stream_id);
+ if (iter == stream_map_.end()) {
+ QUICHE_LOG(ERROR) << "Unable to find stream " << stream_id;
+ return -501; // NGHTTP2_ERR_INVALID_ARGUMENT
+ }
+ StreamState& state = iter->second;
+ if (state.half_closed_local) {
+ QUICHE_LOG(ERROR) << "Stream " << stream_id << " is half closed (local)";
+ return -514; // NGHTTP2_ERR_INVALID_STREAM_STATE
}
- if (serialized.size() > serialized_max) {
- serialized_prefix_ = serialized.substr(serialized_max);
- serialized.resize(serialized_max);
+ if (state.trailers != nullptr) {
+ QUICHE_LOG(ERROR) << "Stream " << stream_id
+ << " already has trailers queued";
+ return -514; // NGHTTP2_ERR_INVALID_STREAM_STATE
+ }
+ if (state.outbound_body == nullptr) {
+ // Enqueue trailers immediately.
+ SendTrailers(stream_id, ToHeaderBlock(trailers));
+ MaybeCloseWithRstStream(stream_id, state);
+ } else {
+ QUICHE_LOG_IF(ERROR, state.outbound_body->send_fin())
+ << "DataFrameSource will send fin, preventing trailers!";
+ // Save trailers so they can be written once data is done.
+ state.trailers =
+ absl::make_unique<spdy::SpdyHeaderBlock>(ToHeaderBlock(trailers));
+ write_scheduler_.MarkStreamReady(stream_id, false);
+ }
+ return 0;
+}
+
+void OgHttp2Session::SubmitMetadata(Http2StreamId stream_id,
+ std::unique_ptr<MetadataSource> source) {
+ if (stream_id == 0) {
+ connection_metadata_.push_back(std::move(source));
+ } else {
+ auto iter = CreateStream(stream_id);
+ iter->second.outbound_metadata.push_back(std::move(source));
+ write_scheduler_.MarkStreamReady(stream_id, false);
}
- return serialized;
}
void OgHttp2Session::OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
@@ -103,32 +578,47 @@ void OgHttp2Session::OnCommonHeader(spdy::SpdyStreamId stream_id,
size_t length,
uint8_t type,
uint8_t flags) {
+ highest_received_stream_id_ = std::max(static_cast<Http2StreamId>(stream_id),
+ highest_received_stream_id_);
visitor_.OnFrameHeader(stream_id, length, type, flags);
}
void OgHttp2Session::OnDataFrameHeader(spdy::SpdyStreamId stream_id,
- size_t length,
- bool fin) {
+ size_t length, bool /*fin*/) {
visitor_.OnBeginDataForStream(stream_id, length);
}
void OgHttp2Session::OnStreamFrameData(spdy::SpdyStreamId stream_id,
const char* data,
size_t len) {
+ MarkDataBuffered(stream_id, len);
visitor_.OnDataForStream(stream_id, absl::string_view(data, len));
}
void OgHttp2Session::OnStreamEnd(spdy::SpdyStreamId stream_id) {
+ auto iter = stream_map_.find(stream_id);
+ if (iter != stream_map_.end()) {
+ iter->second.half_closed_remote = true;
+ }
visitor_.OnEndStream(stream_id);
+ if (iter != stream_map_.end() && iter->second.half_closed_local &&
+ options_.perspective == Perspective::kClient) {
+ // From the client's perspective, the stream can be closed if it's already
+ // half_closed_local.
+ visitor_.OnCloseStream(stream_id, Http2ErrorCode::NO_ERROR);
+ }
}
-void OgHttp2Session::OnStreamPadLength(spdy::SpdyStreamId /*stream_id*/,
- size_t /*value*/) {
- // TODO(181586191): handle padding
+void OgHttp2Session::OnStreamPadLength(spdy::SpdyStreamId stream_id,
+ size_t value) {
+ MarkDataBuffered(stream_id, 1 + value);
+ // TODO(181586191): Pass padding to the visitor?
}
-void OgHttp2Session::OnStreamPadding(spdy::SpdyStreamId stream_id, size_t len) {
- // TODO(181586191): handle padding
+void OgHttp2Session::OnStreamPadding(spdy::SpdyStreamId /*stream_id*/, size_t
+ /*len*/) {
+ // Flow control was accounted for in OnStreamPadLength().
+ // TODO(181586191): Pass padding to the visitor?
}
spdy::SpdyHeadersHandlerInterface* OgHttp2Session::OnHeaderFrameStart(
@@ -137,13 +627,21 @@ spdy::SpdyHeadersHandlerInterface* OgHttp2Session::OnHeaderFrameStart(
return &headers_handler_;
}
-void OgHttp2Session::OnHeaderFrameEnd(spdy::SpdyStreamId stream_id) {
+void OgHttp2Session::OnHeaderFrameEnd(spdy::SpdyStreamId /*stream_id*/) {
headers_handler_.set_stream_id(0);
}
void OgHttp2Session::OnRstStream(spdy::SpdyStreamId stream_id,
spdy::SpdyErrorCode error_code) {
+ auto iter = stream_map_.find(stream_id);
+ if (iter != stream_map_.end()) {
+ iter->second.half_closed_remote = true;
+ iter->second.outbound_body = nullptr;
+ write_scheduler_.UnregisterStream(stream_id);
+ }
visitor_.OnRstStream(stream_id, TranslateErrorCode(error_code));
+ // TODO(birenroy): Consider bundling "close stream" behavior into a dedicated
+ // method that also cleans up the stream map.
visitor_.OnCloseStream(stream_id, TranslateErrorCode(error_code));
}
@@ -153,10 +651,16 @@ void OgHttp2Session::OnSettings() {
void OgHttp2Session::OnSetting(spdy::SpdySettingsId id, uint32_t value) {
visitor_.OnSetting({id, value});
+ if (id == kMetadataExtensionId) {
+ peer_supports_metadata_ = (value != 0);
+ }
}
void OgHttp2Session::OnSettingsEnd() {
visitor_.OnSettingsEnd();
+ auto settings = absl::make_unique<spdy::SpdySettingsIR>();
+ settings->set_is_ack(true);
+ EnqueueFrame(std::move(settings));
}
void OgHttp2Session::OnSettingsAck() {
@@ -174,39 +678,46 @@ void OgHttp2Session::OnGoAway(spdy::SpdyStreamId last_accepted_stream_id,
"");
}
-bool OgHttp2Session::OnGoAwayFrameData(const char* goaway_data, size_t len) {
+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) {}
+ bool /*has_priority*/, int /*weight*/,
+ spdy::SpdyStreamId /*parent_stream_id*/,
+ bool /*exclusive*/, bool /*fin*/, bool /*end*/) {
+ if (options_.perspective == Perspective::kServer) {
+ CreateStream(stream_id);
+ }
+}
void OgHttp2Session::OnWindowUpdate(spdy::SpdyStreamId stream_id,
int delta_window_size) {
if (stream_id == 0) {
- peer_window_ += delta_window_size;
+ connection_send_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 {
+ if (it->second.send_window == 0) {
+ // The stream was blocked on flow control.
+ write_scheduler_.MarkStreamReady(stream_id, false);
+ }
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::OnPushPromise(spdy::SpdyStreamId /*stream_id*/,
+ spdy::SpdyStreamId /*promised_stream_id*/,
+ bool /*end*/) {}
-void OgHttp2Session::OnContinuation(spdy::SpdyStreamId stream_id, bool end) {}
+void OgHttp2Session::OnContinuation(spdy::SpdyStreamId /*stream_id*/, bool
+ /*end*/) {}
void OgHttp2Session::OnAltSvc(spdy::SpdyStreamId /*stream_id*/,
absl::string_view /*origin*/,
@@ -214,18 +725,141 @@ void OgHttp2Session::OnAltSvc(spdy::SpdyStreamId /*stream_id*/,
AlternativeServiceVector& /*altsvc_vector*/) {
}
-void OgHttp2Session::OnPriority(spdy::SpdyStreamId stream_id,
- spdy::SpdyStreamId parent_stream_id,
- int weight,
- bool exclusive) {}
+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) {}
+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) {
+bool OgHttp2Session::OnUnknownFrame(spdy::SpdyStreamId /*stream_id*/,
+ uint8_t /*frame_type*/) {
return true;
}
+void OgHttp2Session::OnHeaderStatus(
+ Http2StreamId stream_id, Http2VisitorInterface::OnHeaderResult result) {
+ QUICHE_DCHECK_NE(result, Http2VisitorInterface::HEADER_OK);
+ if (result == Http2VisitorInterface::HEADER_RST_STREAM) {
+ auto it = streams_reset_.find(stream_id);
+ if (it == streams_reset_.end()) {
+ EnqueueFrame(absl::make_unique<spdy::SpdyRstStreamIR>(
+ stream_id, spdy::ERROR_CODE_INTERNAL_ERROR));
+ }
+ } else if (result == Http2VisitorInterface::HEADER_CONNECTION_ERROR) {
+ visitor_.OnConnectionError();
+ }
+}
+
+bool OgHttp2Session::OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length,
+ uint8_t type, uint8_t flags) {
+ if (type == kMetadataFrameType) {
+ QUICHE_DCHECK_EQ(metadata_length_, 0u);
+ visitor_.OnBeginMetadataForStream(stream_id, length);
+ metadata_stream_id_ = stream_id;
+ metadata_length_ = length;
+ end_metadata_ = flags & kMetadataEndFlag;
+ return true;
+ } else {
+ QUICHE_DLOG(INFO) << "Unexpected frame type " << static_cast<int>(type)
+ << " received by the extension visitor.";
+ return false;
+ }
+}
+
+void OgHttp2Session::OnFramePayload(const char* data, size_t len) {
+ if (metadata_length_ > 0) {
+ QUICHE_DCHECK_LE(len, metadata_length_);
+ visitor_.OnMetadataForStream(metadata_stream_id_,
+ absl::string_view(data, len));
+ metadata_length_ -= len;
+ if (metadata_length_ == 0 && end_metadata_) {
+ visitor_.OnMetadataEndForStream(metadata_stream_id_);
+ metadata_stream_id_ = 0;
+ end_metadata_ = false;
+ }
+ } else {
+ QUICHE_DLOG(INFO) << "Unexpected metadata payload for stream "
+ << metadata_stream_id_;
+ }
+}
+
+void OgHttp2Session::MaybeSetupPreface() {
+ if (!queued_preface_) {
+ if (options_.perspective == Perspective::kClient) {
+ serialized_prefix_.assign(spdy::kHttp2ConnectionHeaderPrefix,
+ spdy::kHttp2ConnectionHeaderPrefixSize);
+ }
+ // First frame must be a non-ack SETTINGS.
+ if (frames_.empty() ||
+ frames_.front()->frame_type() != spdy::SpdyFrameType::SETTINGS ||
+ reinterpret_cast<spdy::SpdySettingsIR*>(frames_.front().get())
+ ->is_ack()) {
+ frames_.push_front(absl::make_unique<spdy::SpdySettingsIR>());
+ }
+ queued_preface_ = true;
+ }
+}
+
+void OgHttp2Session::SendWindowUpdate(Http2StreamId stream_id,
+ size_t update_delta) {
+ EnqueueFrame(
+ absl::make_unique<spdy::SpdyWindowUpdateIR>(stream_id, update_delta));
+}
+
+void OgHttp2Session::SendTrailers(Http2StreamId stream_id,
+ spdy::SpdyHeaderBlock trailers) {
+ auto frame =
+ absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(trailers));
+ frame->set_fin(true);
+ EnqueueFrame(std::move(frame));
+}
+
+void OgHttp2Session::MaybeCloseWithRstStream(Http2StreamId stream_id,
+ StreamState& state) {
+ state.half_closed_local = true;
+ if (options_.perspective == Perspective::kServer) {
+ if (state.half_closed_remote) {
+ visitor_.OnCloseStream(stream_id, Http2ErrorCode::NO_ERROR);
+ } else {
+ // Since the peer has not yet ended the stream, this endpoint should
+ // send a RST_STREAM NO_ERROR. See RFC 7540 Section 8.1.
+ EnqueueFrame(absl::make_unique<spdy::SpdyRstStreamIR>(
+ stream_id, spdy::SpdyErrorCode::ERROR_CODE_NO_ERROR));
+ // Enqueuing the RST_STREAM also invokes OnCloseStream.
+ }
+ // TODO(birenroy): the server adapter should probably delete stream state
+ // when calling visitor_.OnCloseStream.
+ }
+}
+
+void OgHttp2Session::MarkDataBuffered(Http2StreamId stream_id, size_t bytes) {
+ connection_window_manager_.MarkDataBuffered(bytes);
+ auto it = stream_map_.find(stream_id);
+ if (it != stream_map_.end()) {
+ it->second.window_manager.MarkDataBuffered(bytes);
+ }
+}
+
+OgHttp2Session::StreamStateMap::iterator OgHttp2Session::CreateStream(
+ Http2StreamId stream_id) {
+ WindowManager::WindowUpdateListener listener =
+ [this, stream_id](size_t window_update_delta) {
+ SendWindowUpdate(stream_id, window_update_delta);
+ };
+ absl::flat_hash_map<Http2StreamId, StreamState>::iterator iter;
+ bool inserted;
+ std::tie(iter, inserted) = stream_map_.try_emplace(
+ stream_id,
+ StreamState(stream_receive_window_limit_, std::move(listener)));
+ if (inserted) {
+ // Add the stream to the write scheduler.
+ const WriteScheduler::StreamPrecedenceType precedence(3);
+ write_scheduler_.RegisterStream(stream_id, precedence);
+ }
+ return iter;
+}
+
} // namespace adapter
} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h
index b0bc28b7002..77b9da73c38 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h
@@ -3,11 +3,14 @@
#include <list>
+#include "http2/adapter/data_source.h"
#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 "http2/core/priority_write_scheduler.h"
#include "common/platform/api/quiche_bug_tracker.h"
+#include "common/platform/api/quiche_export.h"
#include "spdy/core/http2_frame_decoder_adapter.h"
#include "spdy/core/spdy_framer.h"
@@ -15,33 +18,80 @@ namespace http2 {
namespace adapter {
// This class manages state associated with a single multiplexed HTTP/2 session.
-class OgHttp2Session : public Http2Session,
- public spdy::SpdyFramerVisitorInterface {
+class QUICHE_EXPORT_PRIVATE OgHttp2Session
+ : public Http2Session,
+ public spdy::SpdyFramerVisitorInterface,
+ public spdy::ExtensionVisitorInterface {
public:
- struct Options {
+ struct QUICHE_EXPORT_PRIVATE Options {
Perspective perspective = Perspective::kClient;
};
- OgHttp2Session(Http2VisitorInterface& visitor, Options /*options*/);
+ OgHttp2Session(Http2VisitorInterface& visitor, Options options);
~OgHttp2Session() override;
// Enqueues a frame for transmission to the peer.
void EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame);
- // If |want_write()| returns true, this method will return a non-empty string
- // containing serialized HTTP/2 frames to write to the peer.
- std::string GetBytesToWrite(absl::optional<size_t> max_bytes);
+ // Starts a graceful shutdown sequence. No-op if a GOAWAY has already been
+ // sent.
+ void StartGracefulShutdown();
+
+ // Invokes the visitor's OnReadyToSend() method for serialized frame data.
+ int Send();
+
+ int32_t SubmitRequest(absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source,
+ void* user_data);
+ int SubmitResponse(Http2StreamId stream_id, absl::Span<const Header> headers,
+ std::unique_ptr<DataFrameSource> data_source);
+ int SubmitTrailer(Http2StreamId stream_id, absl::Span<const Header> trailers);
+ void SubmitMetadata(Http2StreamId stream_id,
+ std::unique_ptr<MetadataSource> source);
+
+ bool IsServerSession() const {
+ return options_.perspective == Perspective::kServer;
+ }
+ Http2StreamId GetHighestReceivedStreamId() const {
+ return highest_received_stream_id_;
+ }
+ void SetStreamUserData(Http2StreamId stream_id, void* user_data);
+ void* GetStreamUserData(Http2StreamId stream_id);
+
+ // Resumes a stream that was previously blocked. Returns true on success.
+ bool ResumeStream(Http2StreamId stream_id);
+
+ // Returns the peer's outstanding stream receive window for the given stream.
+ int GetStreamSendWindowSize(Http2StreamId stream_id) const;
+
+ // Returns the current upper bound on the flow control receive window for this
+ // stream.
+ int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const;
+
+ // Returns the outstanding stream receive window, or -1 if the stream does not
+ // exist.
+ int GetStreamReceiveWindowSize(Http2StreamId stream_id) const;
+
+ // Returns the outstanding connection receive window.
+ int GetReceiveWindowSize() const;
+
+ // Returns the size of the HPACK encoder's dynamic table, including the
+ // per-entry overhead from the specification.
+ int GetHpackEncoderDynamicTableSize() const;
+
+ // Returns the size of the HPACK decoder's dynamic table, including the
+ // per-entry overhead from the specification.
+ int GetHpackDecoderDynamicTableSize() const;
// 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 !received_goaway_; }
bool want_write() const override {
- return !frames_.empty() || !serialized_prefix_.empty();
- }
- int GetRemoteWindowSize() const override {
- return peer_window_;
+ return !frames_.empty() || !serialized_prefix_.empty() ||
+ write_scheduler_.HasReadyStreams() || !connection_metadata_.empty();
}
+ int GetRemoteWindowSize() const override { return connection_send_window_; }
// From SpdyFramerVisitorInterface
void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
@@ -72,7 +122,7 @@ class OgHttp2Session : public Http2Session,
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);
+ bool OnGoAwayFrameData(const char* goaway_data, size_t len) override;
void OnHeaders(spdy::SpdyStreamId stream_id,
bool has_priority,
int weight,
@@ -86,10 +136,9 @@ class OgHttp2Session : public Http2Session,
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*/,
+ void OnAltSvc(spdy::SpdyStreamId /*stream_id*/, absl::string_view /*origin*/,
const spdy::SpdyAltSvcWireFormat::
- AlternativeServiceVector& /*altsvc_vector*/);
+ AlternativeServiceVector& /*altsvc_vector*/) override;
void OnPriority(spdy::SpdyStreamId stream_id,
spdy::SpdyStreamId parent_stream_id,
int weight,
@@ -99,40 +148,136 @@ class OgHttp2Session : public Http2Session,
bool OnUnknownFrame(spdy::SpdyStreamId stream_id,
uint8_t frame_type) override;
+ // Invoked when header processing encounters an invalid or otherwise
+ // problematic header.
+ void OnHeaderStatus(Http2StreamId stream_id,
+ Http2VisitorInterface::OnHeaderResult result);
+
+ // Returns true if a recognized extension frame is received.
+ bool OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type,
+ uint8_t flags) override;
+
+ // Handles the payload for a recognized extension frame.
+ void OnFramePayload(const char* data, size_t len) override;
+
private:
- struct StreamState {
+ using MetadataSequence = std::vector<std::unique_ptr<MetadataSource>>;
+ struct QUICHE_EXPORT_PRIVATE StreamState {
+ StreamState(int32_t stream_receive_window,
+ WindowManager::WindowUpdateListener listener)
+ : window_manager(stream_receive_window, std::move(listener)) {}
+
WindowManager window_manager;
- int32_t send_window = 65535;
+ std::unique_ptr<DataFrameSource> outbound_body;
+ MetadataSequence outbound_metadata;
+ std::unique_ptr<spdy::SpdyHeaderBlock> trailers;
+ void* user_data = nullptr;
+ int32_t send_window = kInitialFlowControlWindowSize;
bool half_closed_local = false;
bool half_closed_remote = false;
};
+ using StreamStateMap = absl::flat_hash_map<Http2StreamId, StreamState>;
- class PassthroughHeadersHandler : public spdy::SpdyHeadersHandlerInterface {
+ class QUICHE_EXPORT_PRIVATE PassthroughHeadersHandler
+ : public spdy::SpdyHeadersHandlerInterface {
public:
- explicit PassthroughHeadersHandler(Http2VisitorInterface& visitor)
- : visitor_(visitor) {}
- void set_stream_id(Http2StreamId stream_id) { stream_id_ = stream_id; }
+ explicit PassthroughHeadersHandler(OgHttp2Session& session,
+ Http2VisitorInterface& visitor)
+ : session_(session), visitor_(visitor) {}
+ void set_stream_id(Http2StreamId stream_id) {
+ stream_id_ = stream_id;
+ result_ = Http2VisitorInterface::HEADER_OK;
+ }
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:
+ OgHttp2Session& session_;
Http2VisitorInterface& visitor_;
Http2StreamId stream_id_ = 0;
+ Http2VisitorInterface::OnHeaderResult result_ =
+ Http2VisitorInterface::HEADER_OK;
};
+ // Queues the connection preface, if not already done.
+ void MaybeSetupPreface();
+
+ void SendWindowUpdate(Http2StreamId stream_id, size_t update_delta);
+
+ // Sends queued frames, returning true if all frames were flushed
+ // successfully.
+ bool SendQueuedFrames();
+
+ // Returns false if the connection is write-blocked (due to flow control or
+ // some other reason).
+ bool WriteForStream(Http2StreamId stream_id);
+
+ bool SendMetadata(Http2StreamId stream_id, MetadataSequence& sequence);
+
+ void SendTrailers(Http2StreamId stream_id, spdy::SpdyHeaderBlock trailers);
+
+ // Encapsulates the RST_STREAM NO_ERROR behavior described in RFC 7540
+ // Section 8.1.
+ void MaybeCloseWithRstStream(Http2StreamId stream_id, StreamState& state);
+
+ // Performs flow control accounting for data sent by the peer.
+ void MarkDataBuffered(Http2StreamId stream_id, size_t bytes);
+
+ // Creates a stream and returns an iterator pointing to it.
+ StreamStateMap::iterator CreateStream(Http2StreamId stream_id);
+
+ // Receives events when inbound frames are parsed.
Http2VisitorInterface& visitor_;
+
+ // Encodes outbound frames.
spdy::SpdyFramer framer_{spdy::SpdyFramer::ENABLE_COMPRESSION};
+
+ // Decodes inbound frames.
http2::Http2DecoderAdapter decoder_;
- absl::flat_hash_map<Http2StreamId, StreamState> stream_map_;
+
+ // Maintains the state of all streams known to this session.
+ StreamStateMap stream_map_;
+
+ // Maintains the queue of outbound frames, and any serialized bytes that have
+ // not yet been consumed.
std::list<std::unique_ptr<spdy::SpdyFrameIR>> frames_;
- PassthroughHeadersHandler headers_handler_;
std::string serialized_prefix_;
+
+ // Maintains the set of streams ready to write data to the peer.
+ using WriteScheduler = PriorityWriteScheduler<Http2StreamId>;
+ WriteScheduler write_scheduler_;
+
+ // Delivers header name-value pairs to the visitor.
+ PassthroughHeadersHandler headers_handler_;
+
+ // Tracks the remaining client connection preface, in the case of a server
+ // session.
absl::string_view remaining_preface_;
- int peer_window_ = 65535;
+
+ WindowManager connection_window_manager_;
+
+ absl::flat_hash_set<Http2StreamId> streams_reset_;
+
+ MetadataSequence connection_metadata_;
+
+ Http2StreamId next_stream_id_ = 1;
+ Http2StreamId highest_received_stream_id_ = 0;
+ Http2StreamId metadata_stream_id_ = 0;
+ size_t metadata_length_ = 0;
+ int connection_send_window_ = kInitialFlowControlWindowSize;
+ // The initial flow control receive window size for any newly created streams.
+ int stream_receive_window_limit_ = kInitialFlowControlWindowSize;
+ int max_frame_payload_ = 16384;
Options options_;
bool received_goaway_ = false;
+ bool queued_preface_ = false;
+ bool peer_supports_metadata_ = false;
+ bool end_metadata_ = false;
+
+ // Replace this with a stream ID, for multiple GOAWAY support.
+ bool queued_goaway_ = false;
};
} // namespace adapter
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc
index ab155697d5d..3f24ea200ef 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc
@@ -2,6 +2,7 @@
#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 {
@@ -9,6 +10,7 @@ namespace adapter {
namespace test {
namespace {
+using spdy::SpdyFrameType;
using testing::_;
enum FrameType {
@@ -31,7 +33,9 @@ TEST(OgHttp2SessionTest, ClientConstruction) {
visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
EXPECT_TRUE(session.want_read());
EXPECT_FALSE(session.want_write());
- EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize);
+ EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize);
+ EXPECT_FALSE(session.IsServerSession());
+ EXPECT_EQ(0, session.GetHighestReceivedStreamId());
}
TEST(OgHttp2SessionTest, ClientHandlesFrames) {
@@ -56,43 +60,433 @@ TEST(OgHttp2SessionTest, ClientHandlesFrames) {
EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0));
EXPECT_CALL(visitor, OnWindowUpdate(0, 1000));
- const ssize_t initial_result = session.ProcessBytes(initial_frames);
+ const size_t initial_result = session.ProcessBytes(initial_frames);
EXPECT_EQ(initial_frames.size(), initial_result);
EXPECT_EQ(session.GetRemoteWindowSize(),
- kDefaultInitialStreamWindowSize + 1000);
+ kInitialFlowControlWindowSize + 1000);
+ EXPECT_EQ(0, session.GetHighestReceivedStreamId());
+
+ // Connection has not yet received any data.
+ EXPECT_EQ(kInitialFlowControlWindowSize, session.GetReceiveWindowSize());
+
+ EXPECT_EQ(0, session.GetHpackDecoderDynamicTableSize());
// Should OgHttp2Session require that streams 1 and 3 have been created?
+ // Submit a request to ensure the first stream is created.
+ const char* kSentinel1 = "arbitrary pointer 1";
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true);
+ body1->AppendPayload("This is an example request body.");
+ body1->EndData();
+ int stream_id =
+ session.SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(body1), const_cast<char*>(kSentinel1));
+ EXPECT_EQ(stream_id, 1);
+
const std::string stream_frames =
TestFrameSequence()
- .Headers(1,
+ .Headers(stream_id,
{{":status", "200"},
{"server", "my-fake-server"},
{"date", "Tue, 6 Apr 2021 12:54:01 GMT"}},
/*fin=*/false)
- .Data(1, "This is the response body.")
+ .Data(stream_id, "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, OnFrameHeader(stream_id, _, HEADERS, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(stream_id));
+ EXPECT_CALL(visitor, OnHeaderForStream(stream_id, ":status", "200"));
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."));
+ OnHeaderForStream(stream_id, "server", "my-fake-server"));
+ EXPECT_CALL(visitor, OnHeaderForStream(stream_id, "date",
+ "Tue, 6 Apr 2021 12:54:01 GMT"));
+ EXPECT_CALL(visitor, OnEndHeadersForStream(stream_id));
+ EXPECT_CALL(visitor, OnFrameHeader(stream_id, 26, DATA, 0));
+ EXPECT_CALL(visitor, OnBeginDataForStream(stream_id, 26));
+ EXPECT_CALL(visitor,
+ OnDataForStream(stream_id, "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, ""));
- const ssize_t stream_result = session.ProcessBytes(stream_frames);
+ const size_t stream_result = session.ProcessBytes(stream_frames);
EXPECT_EQ(stream_frames.size(), stream_result);
+ EXPECT_EQ(3, session.GetHighestReceivedStreamId());
+
+ // The first stream is active and has received some data.
+ EXPECT_GT(kInitialFlowControlWindowSize,
+ session.GetStreamReceiveWindowSize(stream_id));
+ // Connection receive window is equivalent to the first stream's.
+ EXPECT_EQ(session.GetReceiveWindowSize(),
+ session.GetStreamReceiveWindowSize(stream_id));
+ // Receive window upper bound is still the initial value.
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ session.GetStreamReceiveWindowLimit(stream_id));
+
+ EXPECT_GT(session.GetHpackDecoderDynamicTableSize(), 0);
+}
+
+// Verifies that a client session enqueues initial SETTINGS if Send() is called
+// before any frames are explicitly queued.
+TEST(OgHttp2SessionTest, ClientEnqueuesSettingsOnSend) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS}));
+}
+
+// Verifies that a client session enqueues initial SETTINGS before whatever
+// frame type is passed to the first invocation of EnqueueFrame().
+TEST(OgHttp2SessionTest, ClientEnqueuesSettingsBeforeOtherFrame) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+ session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42));
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x0, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING}));
+}
+
+// Verifies that if the first call to EnqueueFrame() passes a SETTINGS frame,
+// the client session will not enqueue an additional SETTINGS frame.
+TEST(OgHttp2SessionTest, ClientEnqueuesSettingsOnce) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+ session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>());
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS}));
+}
+
+TEST(OgHttp2SessionTest, ClientSubmitRequest) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+
+ EXPECT_FALSE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
+ // Even though the user has not queued any frames for the session, it should
+ // still send the connection preface.
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ // Initial SETTINGS.
+ EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ const std::string initial_frames =
+ TestFrameSequence().ServerPreface().Serialize();
+ testing::InSequence s;
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+
+ const size_t initial_result = session.ProcessBytes(initial_frames);
+ EXPECT_EQ(initial_frames.size(), initial_result);
+
+ // Session will want to write a SETTINGS ack.
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ EXPECT_EQ(0, session.GetHpackEncoderDynamicTableSize());
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true);
+ body1->AppendPayload("This is an example request body.");
+ body1->EndData();
+ int stream_id =
+ session.SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(body1), const_cast<char*>(kSentinel1));
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(session.want_write());
+ EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
+ result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ visitor.Clear();
+ EXPECT_FALSE(session.want_write());
+
+ // Some data was sent, so the remaining send window size should be less than
+ // the default.
+ EXPECT_LT(session.GetStreamSendWindowSize(stream_id),
+ kInitialFlowControlWindowSize);
+ EXPECT_GT(session.GetStreamSendWindowSize(stream_id), 0);
+ // Send window for a nonexistent stream is not available.
+ EXPECT_EQ(-1, session.GetStreamSendWindowSize(stream_id + 2));
+
+ EXPECT_GT(session.GetHpackEncoderDynamicTableSize(), 0);
+
+ stream_id =
+ session.SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/two"}}),
+ nullptr, nullptr);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(session.want_write());
+ const char* kSentinel2 = "arbitrary pointer 2";
+ EXPECT_EQ(nullptr, session.GetStreamUserData(stream_id));
+ session.SetStreamUserData(stream_id, const_cast<char*>(kSentinel2));
+ EXPECT_EQ(kSentinel2, session.GetStreamUserData(stream_id));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+ result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+
+ // No data was sent (just HEADERS), so the remaining send window size should
+ // still be the default.
+ EXPECT_EQ(session.GetStreamSendWindowSize(stream_id),
+ kInitialFlowControlWindowSize);
+}
+
+// This test exercises the case where the client request body source is read
+// blocked.
+TEST(OgHttp2SessionTest, ClientSubmitRequestWithReadBlock) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true);
+ TestDataFrameSource* body_ref = body1.get();
+ int stream_id =
+ session.SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(body1), const_cast<char*>(kSentinel1));
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(session.want_write());
+ EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS}));
+ // No data frame, as body1 was read blocked.
+ visitor.Clear();
+ EXPECT_FALSE(session.want_write());
+
+ body_ref->AppendPayload("This is an example request body.");
+ body_ref->EndData();
+ EXPECT_TRUE(session.ResumeStream(stream_id));
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
+ result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA}));
+ EXPECT_FALSE(session.want_write());
+
+ // Stream data is done, so this stream cannot be resumed.
+ EXPECT_FALSE(session.ResumeStream(stream_id));
+ EXPECT_FALSE(session.want_write());
+}
+
+// This test exercises the case where the client request body source is read
+// blocked, then ends with an empty DATA frame.
+TEST(OgHttp2SessionTest, ClientSubmitRequestEmptyDataWithFin) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true);
+ TestDataFrameSource* body_ref = body1.get();
+ int stream_id =
+ session.SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(body1), const_cast<char*>(kSentinel1));
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(session.want_write());
+ EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS}));
+ // No data frame, as body1 was read blocked.
+ visitor.Clear();
+ EXPECT_FALSE(session.want_write());
+
+ body_ref->EndData();
+ EXPECT_TRUE(session.ResumeStream(stream_id));
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 0, 0x1, 0));
+
+ result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA}));
+ EXPECT_FALSE(session.want_write());
+
+ // Stream data is done, so this stream cannot be resumed.
+ EXPECT_FALSE(session.ResumeStream(stream_id));
+ EXPECT_FALSE(session.want_write());
+}
+
+// This test exercises the case where the connection to the peer is write
+// blocked.
+TEST(OgHttp2SessionTest, ClientSubmitRequestWithWriteBlock) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true);
+ body1->AppendPayload("This is an example request body.");
+ body1->EndData();
+ int stream_id =
+ session.SubmitRequest(ToHeaders({{":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}}),
+ std::move(body1), const_cast<char*>(kSentinel1));
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(session.want_write());
+ EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id));
+ visitor.set_is_write_blocked(true);
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+
+ EXPECT_THAT(visitor.data(), testing::IsEmpty());
+ EXPECT_TRUE(session.want_write());
+ visitor.set_is_write_blocked(false);
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
+ result = session.Send();
+ EXPECT_EQ(0, result);
+
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS,
+ SpdyFrameType::DATA}));
+ EXPECT_FALSE(session.want_write());
+}
+
+TEST(OgHttp2SessionTest, ClientStartShutdown) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+
+ EXPECT_FALSE(session.want_write());
+
+ // No-op (except for logging) for a client implementation.
+ session.StartGracefulShutdown();
+ EXPECT_FALSE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS}));
}
TEST(OgHttp2SessionTest, ServerConstruction) {
@@ -101,14 +495,18 @@ TEST(OgHttp2SessionTest, ServerConstruction) {
visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
EXPECT_TRUE(session.want_read());
EXPECT_FALSE(session.want_write());
- EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize);
+ EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize);
+ EXPECT_TRUE(session.IsServerSession());
+ EXPECT_EQ(0, session.GetHighestReceivedStreamId());
}
TEST(OgHttp2SessionTest, ServerHandlesFrames) {
- testing::StrictMock<MockHttp2Visitor> visitor;
+ DataSavingVisitor visitor;
OgHttp2Session session(
visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+ EXPECT_EQ(0, session.GetHpackDecoderDynamicTableSize());
+
const std::string frames = TestFrameSequence()
.ClientPreface()
.Ping(42)
@@ -132,6 +530,8 @@ TEST(OgHttp2SessionTest, ServerHandlesFrames) {
.Serialize();
testing::InSequence s;
+ const char* kSentinel1 = "arbitrary pointer 1";
+
// Client preface (empty SETTINGS)
EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
EXPECT_CALL(visitor, OnSettingsStart());
@@ -147,7 +547,10 @@ TEST(OgHttp2SessionTest, ServerHandlesFrames) {
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, OnEndHeadersForStream(1))
+ .WillOnce(testing::InvokeWithoutArgs([&session, kSentinel1]() {
+ session.SetStreamUserData(1, const_cast<char*>(kSentinel1));
+ }));
EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0));
EXPECT_CALL(visitor, OnWindowUpdate(1, 2000));
EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0));
@@ -167,11 +570,410 @@ TEST(OgHttp2SessionTest, ServerHandlesFrames) {
EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0));
EXPECT_CALL(visitor, OnPing(47, false));
- const ssize_t result = session.ProcessBytes(frames);
+ const size_t result = session.ProcessBytes(frames);
EXPECT_EQ(frames.size(), result);
+ EXPECT_EQ(kSentinel1, session.GetStreamUserData(1));
+
+ // The first stream is active and has received some data.
+ EXPECT_GT(kInitialFlowControlWindowSize,
+ session.GetStreamReceiveWindowSize(1));
+ // Connection receive window is equivalent to the first stream's.
+ EXPECT_EQ(session.GetReceiveWindowSize(),
+ session.GetStreamReceiveWindowSize(1));
+ // Receive window upper bound is still the initial value.
+ EXPECT_EQ(kInitialFlowControlWindowSize,
+ session.GetStreamReceiveWindowLimit(1));
+
+ EXPECT_GT(session.GetHpackDecoderDynamicTableSize(), 0);
+
+ // TODO(birenroy): drop stream state when streams are closed. It should no
+ // longer be possible to set user data.
+ const char* kSentinel3 = "another arbitrary pointer";
+ session.SetStreamUserData(3, const_cast<char*>(kSentinel3));
+ EXPECT_EQ(kSentinel3, session.GetStreamUserData(3));
+
EXPECT_EQ(session.GetRemoteWindowSize(),
- kDefaultInitialStreamWindowSize + 1000);
+ kInitialFlowControlWindowSize + 1000);
+ EXPECT_EQ(3, session.GetHighestReceivedStreamId());
+
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ // Some bytes should have been serialized.
+ int send_result = session.Send();
+ EXPECT_EQ(0, send_result);
+ // Initial SETTINGS, SETTINGS ack.
+ // TODO(birenroy): automatically queue PING acks.
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::SETTINGS}));
+}
+
+// Verifies that a server session enqueues initial SETTINGS before whatever
+// frame type is passed to the first invocation of EnqueueFrame().
+TEST(OgHttp2SessionTest, ServerEnqueuesSettingsBeforeOtherFrame) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+ EXPECT_FALSE(session.want_write());
+ session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42));
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x0, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING}));
+}
+
+// Verifies that if the first call to EnqueueFrame() passes a SETTINGS frame,
+// the server session will not enqueue an additional SETTINGS frame.
+TEST(OgHttp2SessionTest, ServerEnqueuesSettingsOnce) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+ EXPECT_FALSE(session.want_write());
+ session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>());
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
+}
+
+TEST(OgHttp2SessionTest, ServerSubmitResponse) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+
+ EXPECT_FALSE(session.want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ const char* kSentinel1 = "arbitrary pointer 1";
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ 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))
+ .WillOnce(testing::InvokeWithoutArgs([&session, kSentinel1]() {
+ session.SetStreamUserData(1, const_cast<char*>(kSentinel1));
+ }));
+ EXPECT_CALL(visitor, OnEndStream(1));
+
+ const size_t result = session.ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ EXPECT_EQ(1, session.GetHighestReceivedStreamId());
+
+ EXPECT_EQ(0, session.GetHpackEncoderDynamicTableSize());
+
+ // Server will want to send initial SETTINGS, and a SETTINGS ack.
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ int send_result = session.Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ EXPECT_FALSE(session.want_write());
+ // A data fin is not sent so that the stream remains open, and the flow
+ // control state can be verified.
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false);
+ body1->AppendPayload("This is an example response body.");
+ int submit_result = session.SubmitResponse(
+ 1,
+ ToHeaders({{":status", "404"},
+ {"x-comment", "I have no idea what you're talking about."}}),
+ std::move(body1));
+ EXPECT_EQ(submit_result, 0);
+ EXPECT_TRUE(session.want_write());
+
+ // Stream user data should have been set successfully after receiving headers.
+ EXPECT_EQ(kSentinel1, session.GetStreamUserData(1));
+ session.SetStreamUserData(1, nullptr);
+ EXPECT_EQ(nullptr, session.GetStreamUserData(1));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ send_result = session.Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA}));
+ EXPECT_FALSE(session.want_write());
+
+ // Some data was sent, so the remaining send window size should be less than
+ // the default.
+ EXPECT_LT(session.GetStreamSendWindowSize(1), kInitialFlowControlWindowSize);
+ EXPECT_GT(session.GetStreamSendWindowSize(1), 0);
+ // Send window for a nonexistent stream is not available.
+ EXPECT_EQ(session.GetStreamSendWindowSize(3), -1);
+
+ EXPECT_GT(session.GetHpackEncoderDynamicTableSize(), 0);
+}
+
+TEST(OgHttp2SessionTest, ServerStartShutdown) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+
+ EXPECT_FALSE(session.want_write());
+
+ session.StartGracefulShutdown();
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY}));
+}
+
+TEST(OgHttp2SessionTest, ServerStartShutdownAfterGoaway) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+
+ EXPECT_FALSE(session.want_write());
+
+ auto goaway = absl::make_unique<spdy::SpdyGoAwayIR>(
+ 1, spdy::ERROR_CODE_NO_ERROR, "and don't come back!");
+ session.EnqueueFrame(std::move(goaway));
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+
+ int result = session.Send();
+ EXPECT_EQ(0, result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY}));
+
+ // No-op, since a GOAWAY has previously been enqueued.
+ session.StartGracefulShutdown();
+ EXPECT_FALSE(session.want_write());
+}
+
+// Tests the case where the server queues trailers after the data stream is
+// exhausted.
+TEST(OgHttp2SessionTest, ServerSendsTrailers) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+
+ EXPECT_FALSE(session.want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ 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, OnEndStream(1));
+
+ const size_t result = session.ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ // Server will want to send initial SETTINGS, and a SETTINGS ack.
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ int send_result = session.Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ EXPECT_FALSE(session.want_write());
+
+ // The body source must indicate that the end of the body is not the end of
+ // the stream.
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false);
+ body1->AppendPayload("This is an example response body.");
+ body1->EndData();
+ int submit_result = session.SubmitResponse(
+ 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}),
+ std::move(body1));
+ EXPECT_EQ(submit_result, 0);
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ send_result = session.Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA}));
+ visitor.Clear();
+ EXPECT_FALSE(session.want_write());
+
+ // The body source has been exhausted by the call to Send() above.
+ // TODO(birenroy): Fix this strange ordering.
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+ int trailer_result = session.SubmitTrailer(
+ 1, ToHeaders({{"final-status", "a-ok"},
+ {"x-comment", "trailers sure are cool"}}));
+ ASSERT_EQ(trailer_result, 0);
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+
+ send_result = session.Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS}));
+}
+
+// Tests the case where the server queues trailers immediately after headers and
+// data, and before any writes have taken place.
+TEST(OgHttp2SessionTest, ServerQueuesTrailersWithResponse) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+
+ EXPECT_FALSE(session.want_write());
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "GET"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/true)
+ .Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // Stream 1
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
+ 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, OnEndStream(1));
+
+ const size_t result = session.ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ // Server will want to send initial SETTINGS, and a SETTINGS ack.
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
+ int send_result = session.Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS}));
+ visitor.Clear();
+
+ EXPECT_FALSE(session.want_write());
+
+ // The body source must indicate that the end of the body is not the end of
+ // the stream.
+ auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false);
+ body1->AppendPayload("This is an example response body.");
+ body1->EndData();
+ int submit_result = session.SubmitResponse(
+ 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}),
+ std::move(body1));
+ EXPECT_EQ(submit_result, 0);
+ EXPECT_TRUE(session.want_write());
+ // There has not been a call to Send() yet, so neither headers nor body have
+ // been written.
+ int trailer_result = session.SubmitTrailer(
+ 1, ToHeaders({{"final-status", "a-ok"},
+ {"x-comment", "trailers sure are cool"}}));
+ ASSERT_EQ(trailer_result, 0);
+ EXPECT_TRUE(session.want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+ EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+ // TODO(birenroy): Fix this strange ordering.
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+ EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+
+ send_result = session.Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA,
+ SpdyFrameType::HEADERS}));
}
} // namespace test
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h
index e222c96ccfd..3134ff7b004 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h
@@ -3,12 +3,14 @@
#include "absl/types/span.h"
#include "http2/adapter/http2_protocol.h"
+#include "common/platform/api/quiche_export.h"
#include "spdy/core/spdy_header_block.h"
namespace http2 {
namespace adapter {
-spdy::SpdyHeaderBlock ToHeaderBlock(absl::Span<const Header> headers);
+QUICHE_EXPORT_PRIVATE spdy::SpdyHeaderBlock ToHeaderBlock(
+ absl::Span<const Header> headers);
} // namespace adapter
} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc b/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc
index 78ec423e25e..75f3749e62c 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc
@@ -7,6 +7,11 @@ namespace http2 {
namespace adapter {
namespace test {
+ssize_t RecordingHttp2Visitor::OnReadyToSend(absl::string_view serialized) {
+ events_.push_back(absl::StrFormat("OnReadyToSend %d", serialized.size()));
+ return serialized.size();
+}
+
void RecordingHttp2Visitor::OnConnectionError() {
events_.push_back("OnConnectionError");
}
@@ -36,15 +41,16 @@ void RecordingHttp2Visitor::OnSettingsAck() {
events_.push_back("OnSettingsAck");
}
-void RecordingHttp2Visitor::OnBeginHeadersForStream(Http2StreamId stream_id) {
+bool RecordingHttp2Visitor::OnBeginHeadersForStream(Http2StreamId stream_id) {
events_.push_back(absl::StrFormat("OnBeginHeadersForStream %d", stream_id));
+ return true;
}
-void RecordingHttp2Visitor::OnHeaderForStream(Http2StreamId stream_id,
- absl::string_view name,
- absl::string_view value) {
+Http2VisitorInterface::OnHeaderResult RecordingHttp2Visitor::OnHeaderForStream(
+ Http2StreamId stream_id, absl::string_view name, absl::string_view value) {
events_.push_back(
absl::StrFormat("OnHeaderForStream %d %s %s", stream_id, name, value));
+ return HEADER_OK;
}
void RecordingHttp2Visitor::OnEndHeadersForStream(Http2StreamId stream_id) {
@@ -112,11 +118,32 @@ void RecordingHttp2Visitor::OnWindowUpdate(Http2StreamId stream_id,
absl::StrFormat("OnWindowUpdate %d %d", stream_id, window_increment));
}
-void RecordingHttp2Visitor::OnReadyToSendDataForStream(Http2StreamId stream_id,
- char* destination_buffer,
- size_t length,
- ssize_t* written,
- bool* end_stream) {
+int RecordingHttp2Visitor::OnBeforeFrameSent(uint8_t frame_type,
+ Http2StreamId stream_id,
+ size_t length, uint8_t flags) {
+ events_.push_back(absl::StrFormat("OnBeforeFrameSent %d %d %d %d", frame_type,
+ stream_id, length, flags));
+ return 0;
+}
+
+int RecordingHttp2Visitor::OnFrameSent(uint8_t frame_type,
+ Http2StreamId stream_id, size_t length,
+ uint8_t flags, uint32_t error_code) {
+ events_.push_back(absl::StrFormat("OnFrameSent %d %d %d %d %d", frame_type,
+ stream_id, length, flags, error_code));
+ return 0;
+}
+
+bool RecordingHttp2Visitor::OnInvalidFrame(Http2StreamId stream_id,
+ int error_code) {
+ events_.push_back(
+ absl::StrFormat("OnInvalidFrame %d %d", stream_id, error_code));
+ return true;
+}
+
+void RecordingHttp2Visitor::OnReadyToSendDataForStream(
+ Http2StreamId stream_id, char* /*destination_buffer*/, size_t length,
+ ssize_t* /*written*/, bool* /*end_stream*/) {
// TODO(b/181586191): Revisit this. The visitor is expected to write to the
// |destination_buffer| and set the other pointer values appropriately.
events_.push_back(
@@ -124,10 +151,8 @@ void RecordingHttp2Visitor::OnReadyToSendDataForStream(Http2StreamId stream_id,
}
void RecordingHttp2Visitor::OnReadyToSendMetadataForStream(
- Http2StreamId stream_id,
- char* buffer,
- size_t length,
- ssize_t* written) {
+ Http2StreamId stream_id, char* /*buffer*/, size_t length,
+ ssize_t* /*written*/) {
// TODO(b/181586191): Revisit this. The visitor is expected to write to the
// |buffer| and set *written appropriately.
events_.push_back(absl::StrFormat("OnReadyToSendMetadataForStream %d %d",
@@ -146,8 +171,13 @@ void RecordingHttp2Visitor::OnMetadataForStream(Http2StreamId stream_id,
absl::StrFormat("OnMetadataForStream %d %s", stream_id, metadata));
}
-void RecordingHttp2Visitor::OnMetadataEndForStream(Http2StreamId stream_id) {
+bool RecordingHttp2Visitor::OnMetadataEndForStream(Http2StreamId stream_id) {
events_.push_back(absl::StrFormat("OnMetadataEndForStream %d", stream_id));
+ return true;
+}
+
+void RecordingHttp2Visitor::OnErrorDebug(absl::string_view message) {
+ events_.push_back(absl::StrFormat("OnErrorDebug %s", message));
}
} // namespace test
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h b/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h
index 452b45185cb..25b92935b35 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h
@@ -5,6 +5,7 @@
#include <string>
#include "http2/adapter/http2_visitor_interface.h"
+#include "common/platform/api/quiche_export.h"
#include "common/platform/api/quiche_test.h"
namespace http2 {
@@ -12,12 +13,13 @@ namespace adapter {
namespace test {
// A visitor implementation that records the sequence of callbacks it receives.
-class RecordingHttp2Visitor : public Http2VisitorInterface {
+class QUICHE_NO_EXPORT RecordingHttp2Visitor : public Http2VisitorInterface {
public:
using Event = std::string;
using EventSequence = std::list<Event>;
// From Http2VisitorInterface
+ ssize_t OnReadyToSend(absl::string_view serialized) override;
void OnConnectionError() override;
void OnFrameHeader(Http2StreamId stream_id,
size_t length,
@@ -27,10 +29,10 @@ class RecordingHttp2Visitor : public Http2VisitorInterface {
void OnSetting(Http2Setting setting) override;
void OnSettingsEnd() override;
void OnSettingsAck() override;
- void OnBeginHeadersForStream(Http2StreamId stream_id) override;
- void OnHeaderForStream(Http2StreamId stream_id,
- absl::string_view name,
- absl::string_view value) override;
+ bool OnBeginHeadersForStream(Http2StreamId stream_id) override;
+ OnHeaderResult OnHeaderForStream(Http2StreamId stream_id,
+ absl::string_view name,
+ absl::string_view value) override;
void OnEndHeadersForStream(Http2StreamId stream_id) override;
void OnBeginDataForStream(Http2StreamId stream_id,
size_t payload_length) override;
@@ -51,6 +53,11 @@ class RecordingHttp2Visitor : public Http2VisitorInterface {
Http2ErrorCode error_code,
absl::string_view opaque_data) override;
void OnWindowUpdate(Http2StreamId stream_id, int window_increment) override;
+ int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+ size_t length, uint8_t flags) override;
+ int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id, size_t length,
+ uint8_t flags, uint32_t error_code) override;
+ bool OnInvalidFrame(Http2StreamId stream_id, int error_code) override;
void OnReadyToSendDataForStream(Http2StreamId stream_id,
char* destination_buffer,
size_t length,
@@ -64,7 +71,8 @@ class RecordingHttp2Visitor : public Http2VisitorInterface {
size_t payload_length) override;
void OnMetadataForStream(Http2StreamId stream_id,
absl::string_view metadata) override;
- void OnMetadataEndForStream(Http2StreamId stream_id) override;
+ bool OnMetadataEndForStream(Http2StreamId stream_id) override;
+ void OnErrorDebug(absl::string_view message) override;
const EventSequence& GetEventSequence() const { return events_; }
void Clear() { events_.clear(); }
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc b/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc
index 7be368ea6a5..bb1517d3d73 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc
@@ -2,6 +2,7 @@
#include "http2/adapter/http2_util.h"
#include "http2/adapter/oghttp2_util.h"
+#include "spdy/core/hpack/hpack_encoder.h"
#include "spdy/core/spdy_framer.h"
namespace http2 {
@@ -11,8 +12,9 @@ namespace test {
std::vector<const Header> ToHeaders(
absl::Span<const std::pair<absl::string_view, absl::string_view>> headers) {
std::vector<const Header> out;
- for (auto [name, value] : headers) {
- out.push_back(std::make_pair(HeaderRep(name), HeaderRep(value)));
+ for (const auto& header : headers) {
+ out.push_back(
+ std::make_pair(HeaderRep(header.first), HeaderRep(header.second)));
}
return out;
}
@@ -88,24 +90,45 @@ TestFrameSequence& TestFrameSequence::GoAway(Http2StreamId last_good_stream_id,
TestFrameSequence& TestFrameSequence::Headers(
Http2StreamId stream_id,
absl::Span<const std::pair<absl::string_view, absl::string_view>> headers,
- bool fin) {
- return Headers(stream_id, ToHeaders(headers), fin);
+ bool fin, bool add_continuation) {
+ return Headers(stream_id, ToHeaders(headers), fin, add_continuation);
}
TestFrameSequence& TestFrameSequence::Headers(Http2StreamId stream_id,
spdy::Http2HeaderBlock block,
- bool fin) {
- auto headers =
- absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(block));
- headers->set_fin(fin);
- frames_.push_back(std::move(headers));
+ bool fin, bool add_continuation) {
+ if (add_continuation) {
+ // The normal intermediate representations don't allow you to represent a
+ // nonterminal HEADERS frame explicitly, so we'll need to use
+ // SpdyUnknownIRs. For simplicity, and in order not to mess up HPACK state,
+ // the payload will be uncompressed.
+ std::string encoded_block;
+ spdy::HpackEncoder encoder;
+ encoder.DisableCompression();
+ encoder.EncodeHeaderSet(block, &encoded_block);
+ const size_t pos = encoded_block.size() / 2;
+ const uint8_t flags = fin ? 0x1 : 0x0;
+ frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
+ stream_id, static_cast<uint8_t>(spdy::SpdyFrameType::HEADERS), flags,
+ encoded_block.substr(0, pos)));
+
+ auto continuation = absl::make_unique<spdy::SpdyContinuationIR>(stream_id);
+ continuation->set_end_headers(true);
+ continuation->take_encoding(encoded_block.substr(pos));
+ frames_.push_back(std::move(continuation));
+ } else {
+ auto headers =
+ absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(block));
+ headers->set_fin(fin);
+ frames_.push_back(std::move(headers));
+ }
return *this;
}
TestFrameSequence& TestFrameSequence::Headers(Http2StreamId stream_id,
absl::Span<const Header> headers,
- bool fin) {
- return Headers(stream_id, ToHeaderBlock(headers), fin);
+ bool fin, bool add_continuation) {
+ return Headers(stream_id, ToHeaderBlock(headers), fin, add_continuation);
}
TestFrameSequence& TestFrameSequence::WindowUpdate(Http2StreamId stream_id,
@@ -124,6 +147,25 @@ TestFrameSequence& TestFrameSequence::Priority(Http2StreamId stream_id,
return *this;
}
+TestFrameSequence& TestFrameSequence::Metadata(Http2StreamId stream_id,
+ absl::string_view payload,
+ bool multiple_frames) {
+ const std::string encoded_payload = MetadataBlockForPayload(payload);
+ if (multiple_frames) {
+ const size_t pos = encoded_payload.size() / 2;
+ frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
+ stream_id, kMetadataFrameType, 0, encoded_payload.substr(0, pos)));
+ frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
+ stream_id, kMetadataFrameType, kMetadataEndFlag,
+ encoded_payload.substr(pos)));
+ } else {
+ frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
+ stream_id, kMetadataFrameType, kMetadataEndFlag,
+ std::move(encoded_payload)));
+ }
+ return *this;
+}
+
std::string TestFrameSequence::Serialize() {
std::string result;
if (!preface_.empty()) {
@@ -137,6 +179,18 @@ std::string TestFrameSequence::Serialize() {
return result;
}
+std::string TestFrameSequence::MetadataBlockForPayload(
+ absl::string_view payload) {
+ // Encode the payload using a header block.
+ spdy::SpdyHeaderBlock block;
+ block["example-payload"] = payload;
+ spdy::HpackEncoder encoder;
+ encoder.DisableCompression();
+ std::string encoded_payload;
+ encoder.EncodeHeaderSet(block, &encoded_payload);
+ return encoded_payload;
+}
+
} // namespace test
} // namespace adapter
} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h b/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h
index cc5e8b5ff57..99740d38f9e 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h
@@ -6,16 +6,17 @@
#include <vector>
#include "http2/adapter/http2_protocol.h"
+#include "common/platform/api/quiche_export.h"
#include "spdy/core/spdy_protocol.h"
namespace http2 {
namespace adapter {
namespace test {
-std::vector<const Header> ToHeaders(
+std::vector<const Header> QUICHE_NO_EXPORT ToHeaders(
absl::Span<const std::pair<absl::string_view, absl::string_view>> headers);
-class TestFrameSequence {
+class QUICHE_NO_EXPORT TestFrameSequence {
public:
TestFrameSequence() = default;
@@ -36,21 +37,26 @@ class TestFrameSequence {
TestFrameSequence& Headers(
Http2StreamId stream_id,
absl::Span<const std::pair<absl::string_view, absl::string_view>> headers,
- bool fin = false);
+ bool fin = false, bool add_continuation = false);
TestFrameSequence& Headers(Http2StreamId stream_id,
- spdy::Http2HeaderBlock block,
- bool fin = false);
+ spdy::Http2HeaderBlock block, bool fin = false,
+ bool add_continuation = false);
TestFrameSequence& Headers(Http2StreamId stream_id,
- absl::Span<const Header> headers,
- bool fin = false);
+ absl::Span<const Header> headers, bool fin = false,
+ bool add_continuation = false);
TestFrameSequence& WindowUpdate(Http2StreamId stream_id, int32_t delta);
TestFrameSequence& Priority(Http2StreamId stream_id,
Http2StreamId parent_stream_id,
int weight,
bool exclusive);
+ TestFrameSequence& Metadata(Http2StreamId stream_id,
+ absl::string_view payload,
+ bool multiple_frames = false);
std::string Serialize();
+ static std::string MetadataBlockForPayload(absl::string_view);
+
private:
std::string preface_;
std::vector<std::unique_ptr<spdy::SpdyFrameIR>> frames_;
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc b/chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc
index 315a28f0717..b8acddbb87f 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc
@@ -1,28 +1,123 @@
#include "http2/adapter/test_utils.h"
-#include "http2/adapter/nghttp2_util.h"
+#include <ostream>
+
+#include "absl/strings/str_format.h"
#include "common/quiche_endian.h"
+#include "spdy/core/hpack/hpack_encoder.h"
#include "spdy/core/spdy_frame_reader.h"
namespace http2 {
namespace adapter {
namespace test {
+
+TestDataFrameSource::TestDataFrameSource(Http2VisitorInterface& visitor,
+ bool has_fin)
+ : visitor_(visitor), has_fin_(has_fin) {}
+
+void TestDataFrameSource::AppendPayload(absl::string_view payload) {
+ QUICHE_CHECK(!end_data_);
+ if (!payload.empty()) {
+ payload_fragments_.push_back(std::string(payload));
+ current_fragment_ = payload_fragments_.front();
+ }
+}
+
+void TestDataFrameSource::EndData() { end_data_ = true; }
+
+std::pair<ssize_t, bool> TestDataFrameSource::SelectPayloadLength(
+ size_t max_length) {
+ // The stream is done if there's no more data, or if |max_length| is at least
+ // as large as the remaining data.
+ const bool end_data = end_data_ && (current_fragment_.empty() ||
+ (payload_fragments_.size() == 1 &&
+ max_length >= current_fragment_.size()));
+ const ssize_t length = std::min(max_length, current_fragment_.size());
+ return {length, end_data};
+}
+
+bool TestDataFrameSource::Send(absl::string_view frame_header,
+ size_t payload_length) {
+ QUICHE_LOG_IF(DFATAL, payload_length > current_fragment_.size())
+ << "payload_length: " << payload_length
+ << " current_fragment_size: " << current_fragment_.size();
+ const std::string concatenated =
+ absl::StrCat(frame_header, current_fragment_.substr(0, payload_length));
+ const ssize_t result = visitor_.OnReadyToSend(concatenated);
+ if (result < 0) {
+ // Write encountered error.
+ visitor_.OnConnectionError();
+ current_fragment_ = {};
+ payload_fragments_.clear();
+ return false;
+ } else if (result == 0) {
+ // Write blocked.
+ return false;
+ } else if (static_cast<const size_t>(result) < concatenated.size()) {
+ // Probably need to handle this better within this test class.
+ QUICHE_LOG(DFATAL)
+ << "DATA frame not fully flushed. Connection will be corrupt!";
+ visitor_.OnConnectionError();
+ current_fragment_ = {};
+ payload_fragments_.clear();
+ return false;
+ }
+ if (payload_length > 0) {
+ current_fragment_.remove_prefix(payload_length);
+ }
+ if (current_fragment_.empty() && !payload_fragments_.empty()) {
+ payload_fragments_.erase(payload_fragments_.begin());
+ if (!payload_fragments_.empty()) {
+ current_fragment_ = payload_fragments_.front();
+ }
+ }
+ return true;
+}
+
+std::string EncodeHeaders(const spdy::SpdyHeaderBlock& entries) {
+ spdy::HpackEncoder encoder;
+ encoder.DisableCompression();
+ std::string result;
+ QUICHE_CHECK(encoder.EncodeHeaderSet(entries, &result));
+ return result;
+}
+
+TestMetadataSource::TestMetadataSource(const spdy::SpdyHeaderBlock& entries)
+ : encoded_entries_(EncodeHeaders(entries)) {
+ remaining_ = encoded_entries_;
+}
+
+std::pair<ssize_t, bool> TestMetadataSource::Pack(uint8_t* dest,
+ size_t dest_len) {
+ const size_t copied = std::min(dest_len, remaining_.size());
+ std::memcpy(dest, remaining_.data(), copied);
+ remaining_.remove_prefix(copied);
+ return std::make_pair(copied, remaining_.empty());
+}
+
namespace {
using TypeAndOptionalLength =
std::pair<spdy::SpdyFrameType, absl::optional<size_t>>;
-std::vector<std::pair<const char*, std::string>> LogFriendly(
+std::ostream& operator<<(
+ std::ostream& os,
const std::vector<TypeAndOptionalLength>& types_and_lengths) {
- std::vector<std::pair<const char*, std::string>> out;
- out.reserve(types_and_lengths.size());
- for (const auto type_and_length : types_and_lengths) {
- out.push_back({spdy::FrameTypeToString(type_and_length.first),
- type_and_length.second
- ? absl::StrCat(type_and_length.second.value())
- : "<unspecified>"});
+ for (const auto& type_and_length : types_and_lengths) {
+ os << "(" << spdy::FrameTypeToString(type_and_length.first) << ", "
+ << (type_and_length.second ? absl::StrCat(type_and_length.second.value())
+ : "<unspecified>")
+ << ") ";
+ }
+ return os;
+}
+
+std::string FrameTypeToString(uint8_t frame_type) {
+ if (spdy::IsDefinedFrameType(frame_type)) {
+ return spdy::FrameTypeToString(spdy::ParseFrameType(frame_type));
+ } else {
+ return absl::StrFormat("0x%x", static_cast<int>(frame_type));
}
- return out;
}
// Custom gMock matcher, used to implement EqualsFrames().
@@ -75,16 +170,8 @@ class SpdyControlFrameMatcher
return false;
}
- if (!spdy::IsDefinedFrameType(raw_type)) {
- *listener << "; expected type " << FrameTypeToString(expected_type)
- << " but raw type " << static_cast<int>(raw_type)
- << " is not a defined frame type!";
- return false;
- }
-
- spdy::SpdyFrameType actual_type = spdy::ParseFrameType(raw_type);
- if (actual_type != expected_type) {
- *listener << "; actual type: " << FrameTypeToString(actual_type)
+ if (raw_type != static_cast<uint8_t>(expected_type)) {
+ *listener << "; actual type: " << FrameTypeToString(raw_type)
<< " but expected type: " << FrameTypeToString(expected_type);
return false;
}
@@ -96,358 +183,18 @@ class SpdyControlFrameMatcher
void DescribeTo(std::ostream* os) const override {
*os << "Data contains frames of types in sequence "
- << LogFriendly(expected_types_and_lengths_);
+ << expected_types_and_lengths_;
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "Data does not contain frames of types in sequence "
- << LogFriendly(expected_types_and_lengths_);
+ << expected_types_and_lengths_;
}
private:
const std::vector<TypeAndOptionalLength> expected_types_and_lengths_;
};
-// Custom gMock matcher, used to implement HasFrameHeader().
-class FrameHeaderMatcher
- : public testing::MatcherInterface<const nghttp2_frame_hd*> {
- public:
- FrameHeaderMatcher(int32_t streamid,
- uint8_t type,
- const testing::Matcher<int> flags)
- : stream_id_(streamid), type_(type), flags_(flags) {}
-
- bool MatchAndExplain(const nghttp2_frame_hd* frame,
- testing::MatchResultListener* listener) const override {
- bool matched = true;
- if (stream_id_ != frame->stream_id) {
- *listener << "; expected stream " << stream_id_ << ", saw "
- << frame->stream_id;
- matched = false;
- }
- if (type_ != frame->type) {
- *listener << "; expected frame type " << type_ << ", saw "
- << static_cast<int>(frame->type);
- matched = false;
- }
- if (!flags_.MatchAndExplain(frame->flags, listener)) {
- matched = false;
- }
- return matched;
- }
-
- void DescribeTo(std::ostream* os) const override {
- *os << "contains a frame header with stream " << stream_id_ << ", type "
- << type_ << ", ";
- flags_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "does not contain a frame header with stream " << stream_id_
- << ", type " << type_ << ", ";
- flags_.DescribeNegationTo(os);
- }
-
- private:
- const int32_t stream_id_;
- const int type_;
- const testing::Matcher<int> flags_;
-};
-
-class DataMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
- public:
- DataMatcher(const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<size_t> length,
- const testing::Matcher<int> flags)
- : stream_id_(stream_id), length_(length), flags_(flags) {}
-
- bool MatchAndExplain(const nghttp2_frame* frame,
- testing::MatchResultListener* listener) const override {
- if (frame->hd.type != NGHTTP2_DATA) {
- *listener << "; expected DATA frame, saw frame of type "
- << static_cast<int>(frame->hd.type);
- return false;
- }
- bool matched = true;
- if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
- matched = false;
- }
- if (!length_.MatchAndExplain(frame->hd.length, listener)) {
- matched = false;
- }
- if (!flags_.MatchAndExplain(frame->hd.flags, listener)) {
- matched = false;
- }
- return matched;
- }
-
- void DescribeTo(std::ostream* os) const override {
- *os << "contains a DATA frame, ";
- stream_id_.DescribeTo(os);
- length_.DescribeTo(os);
- flags_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "does not contain a DATA frame, ";
- stream_id_.DescribeNegationTo(os);
- length_.DescribeNegationTo(os);
- flags_.DescribeNegationTo(os);
- }
-
- private:
- const testing::Matcher<uint32_t> stream_id_;
- const testing::Matcher<size_t> length_;
- const testing::Matcher<int> flags_;
-};
-
-class HeadersMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
- public:
- HeadersMatcher(const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<int> flags,
- const testing::Matcher<int> category)
- : stream_id_(stream_id), flags_(flags), category_(category) {}
-
- bool MatchAndExplain(const nghttp2_frame* frame,
- testing::MatchResultListener* listener) const override {
- if (frame->hd.type != NGHTTP2_HEADERS) {
- *listener << "; expected HEADERS frame, saw frame of type "
- << static_cast<int>(frame->hd.type);
- return false;
- }
- bool matched = true;
- if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
- matched = false;
- }
- if (!flags_.MatchAndExplain(frame->hd.flags, listener)) {
- matched = false;
- }
- if (!category_.MatchAndExplain(frame->headers.cat, listener)) {
- matched = false;
- }
- return matched;
- }
-
- void DescribeTo(std::ostream* os) const override {
- *os << "contains a HEADERS frame, ";
- stream_id_.DescribeTo(os);
- flags_.DescribeTo(os);
- category_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "does not contain a HEADERS frame, ";
- stream_id_.DescribeNegationTo(os);
- flags_.DescribeNegationTo(os);
- category_.DescribeNegationTo(os);
- }
-
- private:
- const testing::Matcher<uint32_t> stream_id_;
- const testing::Matcher<int> flags_;
- const testing::Matcher<int> category_;
-};
-
-class RstStreamMatcher
- : public testing::MatcherInterface<const nghttp2_frame*> {
- public:
- RstStreamMatcher(const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<uint32_t> error_code)
- : stream_id_(stream_id), error_code_(error_code) {}
-
- bool MatchAndExplain(const nghttp2_frame* frame,
- testing::MatchResultListener* listener) const override {
- if (frame->hd.type != NGHTTP2_RST_STREAM) {
- *listener << "; expected RST_STREAM frame, saw frame of type "
- << static_cast<int>(frame->hd.type);
- return false;
- }
- bool matched = true;
- if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
- matched = false;
- }
- if (!error_code_.MatchAndExplain(frame->rst_stream.error_code, listener)) {
- matched = false;
- }
- return matched;
- }
-
- void DescribeTo(std::ostream* os) const override {
- *os << "contains a RST_STREAM frame, ";
- stream_id_.DescribeTo(os);
- error_code_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "does not contain a RST_STREAM frame, ";
- stream_id_.DescribeNegationTo(os);
- error_code_.DescribeNegationTo(os);
- }
-
- private:
- const testing::Matcher<uint32_t> stream_id_;
- const testing::Matcher<uint32_t> error_code_;
-};
-
-class SettingsMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
- public:
- SettingsMatcher(const testing::Matcher<std::vector<Http2Setting>> values)
- : values_(values) {}
-
- bool MatchAndExplain(const nghttp2_frame* frame,
- testing::MatchResultListener* listener) const override {
- if (frame->hd.type != NGHTTP2_SETTINGS) {
- *listener << "; expected SETTINGS frame, saw frame of type "
- << static_cast<int>(frame->hd.type);
- return false;
- }
- std::vector<Http2Setting> settings;
- settings.reserve(frame->settings.niv);
- for (int i = 0; i < frame->settings.niv; ++i) {
- const auto& p = frame->settings.iv[i];
- settings.push_back({static_cast<uint16_t>(p.settings_id), p.value});
- }
- return values_.MatchAndExplain(settings, listener);
- }
-
- void DescribeTo(std::ostream* os) const override {
- *os << "contains a SETTINGS frame, ";
- values_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "does not contain a SETTINGS frame, ";
- values_.DescribeNegationTo(os);
- }
-
- private:
- const testing::Matcher<std::vector<Http2Setting>> values_;
-};
-
-class PingMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
- public:
- PingMatcher(const testing::Matcher<uint64_t> id, bool is_ack)
- : id_(id), is_ack_(is_ack) {}
-
- bool MatchAndExplain(const nghttp2_frame* frame,
- testing::MatchResultListener* listener) const override {
- if (frame->hd.type != NGHTTP2_PING) {
- *listener << "; expected PING frame, saw frame of type "
- << static_cast<int>(frame->hd.type);
- return false;
- }
- bool matched = true;
- bool frame_ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
- if (is_ack_ != frame_ack) {
- *listener << "; expected is_ack=" << is_ack_ << ", saw " << frame_ack;
- matched = false;
- }
- uint64_t data;
- std::memcpy(&data, frame->ping.opaque_data, sizeof(data));
- data = quiche::QuicheEndian::HostToNet64(data);
- if (!id_.MatchAndExplain(data, listener)) {
- matched = false;
- }
- return matched;
- }
-
- void DescribeTo(std::ostream* os) const override {
- *os << "contains a PING frame, ";
- id_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "does not contain a PING frame, ";
- id_.DescribeNegationTo(os);
- }
-
- private:
- const testing::Matcher<uint64_t> id_;
- const bool is_ack_;
-};
-
-class GoAwayMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
- public:
- GoAwayMatcher(const testing::Matcher<uint32_t> last_stream_id,
- const testing::Matcher<uint32_t> error_code,
- const testing::Matcher<absl::string_view> opaque_data)
- : last_stream_id_(last_stream_id),
- error_code_(error_code),
- opaque_data_(opaque_data) {}
-
- bool MatchAndExplain(const nghttp2_frame* frame,
- testing::MatchResultListener* listener) const override {
- if (frame->hd.type != NGHTTP2_GOAWAY) {
- *listener << "; expected GOAWAY frame, saw frame of type "
- << static_cast<int>(frame->hd.type);
- return false;
- }
- bool matched = true;
- if (!last_stream_id_.MatchAndExplain(frame->goaway.last_stream_id,
- listener)) {
- matched = false;
- }
- if (!error_code_.MatchAndExplain(frame->goaway.error_code, listener)) {
- matched = false;
- }
- auto opaque_data =
- ToStringView(frame->goaway.opaque_data, frame->goaway.opaque_data_len);
- if (!opaque_data_.MatchAndExplain(opaque_data, listener)) {
- matched = false;
- }
- return matched;
- }
-
- void DescribeTo(std::ostream* os) const override {
- *os << "contains a GOAWAY frame, ";
- last_stream_id_.DescribeTo(os);
- error_code_.DescribeTo(os);
- opaque_data_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "does not contain a GOAWAY frame, ";
- last_stream_id_.DescribeNegationTo(os);
- error_code_.DescribeNegationTo(os);
- opaque_data_.DescribeNegationTo(os);
- }
-
- private:
- const testing::Matcher<uint32_t> last_stream_id_;
- const testing::Matcher<uint32_t> error_code_;
- const testing::Matcher<absl::string_view> opaque_data_;
-};
-
-class WindowUpdateMatcher
- : public testing::MatcherInterface<const nghttp2_frame*> {
- public:
- WindowUpdateMatcher(const testing::Matcher<uint32_t> delta) : delta_(delta) {}
-
- bool MatchAndExplain(const nghttp2_frame* frame,
- testing::MatchResultListener* listener) const override {
- if (frame->hd.type != NGHTTP2_WINDOW_UPDATE) {
- *listener << "; expected WINDOW_UPDATE frame, saw frame of type "
- << static_cast<int>(frame->hd.type);
- return false;
- }
- return delta_.MatchAndExplain(frame->window_update.window_size_increment,
- listener);
- }
-
- void DescribeTo(std::ostream* os) const override {
- *os << "contains a WINDOW_UPDATE frame, ";
- delta_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "does not contain a WINDOW_UPDATE frame, ";
- delta_.DescribeNegationTo(os);
- }
-
- private:
- const testing::Matcher<uint32_t> delta_;
-};
-
} // namespace
testing::Matcher<absl::string_view> EqualsFrames(
@@ -467,61 +214,6 @@ testing::Matcher<absl::string_view> EqualsFrames(
return MakeMatcher(new SpdyControlFrameMatcher(std::move(types_and_lengths)));
}
-testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader(
- uint32_t streamid,
- uint8_t type,
- const testing::Matcher<int> flags) {
- return MakeMatcher(new FrameHeaderMatcher(streamid, type, flags));
-}
-
-testing::Matcher<const nghttp2_frame*> IsData(
- const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<size_t> length,
- const testing::Matcher<int> flags) {
- return MakeMatcher(new DataMatcher(stream_id, length, flags));
-}
-
-testing::Matcher<const nghttp2_frame*> IsHeaders(
- const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<int> flags,
- const testing::Matcher<int> category) {
- return MakeMatcher(new HeadersMatcher(stream_id, flags, category));
-}
-
-testing::Matcher<const nghttp2_frame*> IsRstStream(
- const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<uint32_t> error_code) {
- return MakeMatcher(new RstStreamMatcher(stream_id, error_code));
-}
-
-testing::Matcher<const nghttp2_frame*> IsSettings(
- const testing::Matcher<std::vector<Http2Setting>> values) {
- return MakeMatcher(new SettingsMatcher(values));
-}
-
-testing::Matcher<const nghttp2_frame*> IsPing(
- const testing::Matcher<uint64_t> id) {
- return MakeMatcher(new PingMatcher(id, false));
-}
-
-testing::Matcher<const nghttp2_frame*> IsPingAck(
- const testing::Matcher<uint64_t> id) {
- return MakeMatcher(new PingMatcher(id, true));
-}
-
-testing::Matcher<const nghttp2_frame*> IsGoAway(
- const testing::Matcher<uint32_t> last_stream_id,
- const testing::Matcher<uint32_t> error_code,
- const testing::Matcher<absl::string_view> opaque_data) {
- return MakeMatcher(
- new GoAwayMatcher(last_stream_id, error_code, opaque_data));
-}
-
-testing::Matcher<const nghttp2_frame*> IsWindowUpdate(
- const testing::Matcher<uint32_t> delta) {
- return MakeMatcher(new WindowUpdateMatcher(delta));
-}
-
} // namespace test
} // namespace adapter
} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/test_utils.h b/chromium/net/third_party/quiche/src/http2/adapter/test_utils.h
index ef1ae29a650..d92617fa3c9 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/test_utils.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/test_utils.h
@@ -4,26 +4,99 @@
#include <string>
#include <vector>
+#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
+#include "http2/adapter/data_source.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_export.h"
#include "common/platform/api/quiche_test.h"
+#include "spdy/core/spdy_header_block.h"
#include "spdy/core/spdy_protocol.h"
namespace http2 {
namespace adapter {
namespace test {
-class DataSavingVisitor : public testing::StrictMock<MockHttp2Visitor> {
+class QUICHE_NO_EXPORT DataSavingVisitor
+ : public testing::StrictMock<MockHttp2Visitor> {
public:
- void Save(absl::string_view data) { absl::StrAppend(&data_, data); }
+ ssize_t OnReadyToSend(absl::string_view data) override {
+ if (is_write_blocked_) {
+ return kSendBlocked;
+ }
+ const size_t to_accept = std::min(send_limit_, data.size());
+ if (to_accept == 0) {
+ return kSendBlocked;
+ }
+ absl::StrAppend(&data_, data.substr(0, to_accept));
+ return to_accept;
+ }
+
+ void OnMetadataForStream(Http2StreamId stream_id,
+ absl::string_view metadata) override {
+ testing::StrictMock<MockHttp2Visitor>::OnMetadataForStream(stream_id,
+ metadata);
+ auto result =
+ metadata_map_.try_emplace(stream_id, std::vector<std::string>());
+ result.first->second.push_back(std::string(metadata));
+ }
+
+ const std::vector<std::string> GetMetadata(Http2StreamId stream_id) {
+ auto it = metadata_map_.find(stream_id);
+ if (it == metadata_map_.end()) {
+ return {};
+ } else {
+ return it->second;
+ }
+ }
const std::string& data() { return data_; }
void Clear() { data_.clear(); }
+ void set_send_limit(size_t limit) { send_limit_ = limit; }
+
+ bool is_write_blocked() const { return is_write_blocked_; }
+ void set_is_write_blocked(bool value) { is_write_blocked_ = value; }
+
private:
std::string data_;
+ absl::flat_hash_map<Http2StreamId, std::vector<std::string>> metadata_map_;
+ size_t send_limit_ = std::numeric_limits<size_t>::max();
+ bool is_write_blocked_ = false;
+};
+
+// A test DataFrameSource. Starts out in the empty, blocked state.
+class QUICHE_NO_EXPORT TestDataFrameSource : public DataFrameSource {
+ public:
+ TestDataFrameSource(Http2VisitorInterface& visitor, bool has_fin);
+
+ void AppendPayload(absl::string_view payload);
+ void EndData();
+
+ std::pair<ssize_t, bool> SelectPayloadLength(size_t max_length) override;
+ bool Send(absl::string_view frame_header, size_t payload_length) override;
+ bool send_fin() const override { return has_fin_; }
+
+ private:
+ Http2VisitorInterface& visitor_;
+ std::vector<std::string> payload_fragments_;
+ absl::string_view current_fragment_;
+ // Whether the stream should end with the final frame of data.
+ const bool has_fin_;
+ // Whether |payload_fragments_| contains the final segment of data.
+ bool end_data_ = false;
+};
+
+class QUICHE_NO_EXPORT TestMetadataSource : public MetadataSource {
+ public:
+ explicit TestMetadataSource(const spdy::SpdyHeaderBlock& entries);
+
+ std::pair<ssize_t, bool> Pack(uint8_t* dest, size_t dest_len) override;
+
+ private:
+ const std::string encoded_entries_;
+ absl::string_view remaining_;
};
// These matchers check whether a string consists entirely of HTTP/2 frames of
@@ -41,42 +114,6 @@ testing::Matcher<absl::string_view> EqualsFrames(
testing::Matcher<absl::string_view> EqualsFrames(
std::vector<spdy::SpdyFrameType> types);
-testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader(
- uint32_t streamid,
- uint8_t type,
- const testing::Matcher<int> flags);
-
-testing::Matcher<const nghttp2_frame*> IsData(
- const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<size_t> length,
- const testing::Matcher<int> flags);
-
-testing::Matcher<const nghttp2_frame*> IsHeaders(
- const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<int> flags,
- const testing::Matcher<int> category);
-
-testing::Matcher<const nghttp2_frame*> IsRstStream(
- const testing::Matcher<uint32_t> stream_id,
- const testing::Matcher<uint32_t> error_code);
-
-testing::Matcher<const nghttp2_frame*> IsSettings(
- const testing::Matcher<std::vector<Http2Setting>> values);
-
-testing::Matcher<const nghttp2_frame*> IsPing(
- const testing::Matcher<uint64_t> id);
-
-testing::Matcher<const nghttp2_frame*> IsPingAck(
- const testing::Matcher<uint64_t> id);
-
-testing::Matcher<const nghttp2_frame*> IsGoAway(
- const testing::Matcher<uint32_t> last_stream_id,
- const testing::Matcher<uint32_t> error_code,
- const testing::Matcher<absl::string_view> opaque_data);
-
-testing::Matcher<const nghttp2_frame*> IsWindowUpdate(
- const testing::Matcher<uint32_t> delta);
-
} // namespace test
} // namespace adapter
} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/window_manager.h b/chromium/net/third_party/quiche/src/http2/adapter/window_manager.h
index 277c24f960e..f15982d466a 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/window_manager.h
+++ b/chromium/net/third_party/quiche/src/http2/adapter/window_manager.h
@@ -3,6 +3,8 @@
#include <functional>
+#include "common/platform/api/quiche_export.h"
+
namespace http2 {
namespace adapter {
@@ -12,7 +14,7 @@ class WindowManagerPeer;
// This class keeps track of a HTTP/2 flow control window, notifying a listener
// when a window update needs to be sent. This class is not thread-safe.
-class WindowManager {
+class QUICHE_EXPORT_PRIVATE WindowManager {
public:
// A WindowUpdateListener is invoked when it is time to send a window update.
typedef std::function<void(size_t)> WindowUpdateListener;
diff --git a/chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc
index 5e0645365ec..e706156db69 100644
--- a/chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc
+++ b/chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc
@@ -90,21 +90,21 @@ TEST_F(WindowManagerTest, AvoidWindowUnderflow) {
EXPECT_EQ(wm_.CurrentWindowSize(), wm_.WindowSizeLimit());
// Don't buffer more than the total window!
wm_.MarkDataBuffered(wm_.WindowSizeLimit() + 1);
- EXPECT_EQ(wm_.CurrentWindowSize(), 0);
+ EXPECT_EQ(wm_.CurrentWindowSize(), 0u);
}
// Window manager should GFE_BUG and avoid buffered underflow.
TEST_F(WindowManagerTest, AvoidBufferedUnderflow) {
- EXPECT_EQ(peer_.buffered(), 0);
+ EXPECT_EQ(peer_.buffered(), 0u);
// Don't flush more than has been buffered!
EXPECT_QUICHE_BUG(wm_.MarkDataFlushed(1), "buffered underflow");
- EXPECT_EQ(peer_.buffered(), 0);
+ EXPECT_EQ(peer_.buffered(), 0u);
wm_.MarkDataBuffered(42);
- EXPECT_EQ(peer_.buffered(), 42);
+ EXPECT_EQ(peer_.buffered(), 42u);
// Don't flush more than has been buffered!
EXPECT_QUICHE_BUG(wm_.MarkDataFlushed(43), "buffered underflow");
- EXPECT_EQ(peer_.buffered(), 0);
+ EXPECT_EQ(peer_.buffered(), 0u);
}
// This test verifies that WindowManager notifies its listener when window is