1 #include "quiche/http2/adapter/nghttp2_adapter.h"
2
3 #include <memory>
4
5 #include "absl/algorithm/container.h"
6 #include "absl/strings/str_cat.h"
7 #include "absl/strings/string_view.h"
8 #include "quiche/http2/adapter/http2_visitor_interface.h"
9 #include "quiche/http2/adapter/nghttp2.h"
10 #include "quiche/http2/adapter/nghttp2_callbacks.h"
11 #include "quiche/http2/adapter/nghttp2_data_provider.h"
12 #include "quiche/common/platform/api/quiche_logging.h"
13 #include "quiche/common/quiche_endian.h"
14
15 namespace http2 {
16 namespace adapter {
17
18 namespace {
19
20 using ConnectionError = Http2VisitorInterface::ConnectionError;
21
22 } // anonymous namespace
23
24 // A metadata source that notifies the owning NgHttp2Adapter upon completion or
25 // failure.
26 class NgHttp2Adapter::NotifyingMetadataSource : public MetadataSource {
27 public:
NotifyingMetadataSource(NgHttp2Adapter * adapter,Http2StreamId stream_id,std::unique_ptr<MetadataSource> source)28 explicit NotifyingMetadataSource(NgHttp2Adapter* adapter,
29 Http2StreamId stream_id,
30 std::unique_ptr<MetadataSource> source)
31 : adapter_(adapter), stream_id_(stream_id), source_(std::move(source)) {}
32
NumFrames(size_t max_frame_size) const33 size_t NumFrames(size_t max_frame_size) const override {
34 return source_->NumFrames(max_frame_size);
35 }
36
Pack(uint8_t * dest,size_t dest_len)37 std::pair<int64_t, bool> Pack(uint8_t* dest, size_t dest_len) override {
38 const auto result = source_->Pack(dest, dest_len);
39 if (result.first < 0 || result.second) {
40 adapter_->RemovePendingMetadata(stream_id_);
41 }
42 return result;
43 }
44
OnFailure()45 void OnFailure() override {
46 source_->OnFailure();
47 adapter_->RemovePendingMetadata(stream_id_);
48 }
49
50 private:
51 NgHttp2Adapter* const adapter_;
52 const Http2StreamId stream_id_;
53 std::unique_ptr<MetadataSource> source_;
54 };
55
56 /* static */
CreateClientAdapter(Http2VisitorInterface & visitor,const nghttp2_option * options)57 std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateClientAdapter(
58 Http2VisitorInterface& visitor, const nghttp2_option* options) {
59 auto adapter = new NgHttp2Adapter(visitor, Perspective::kClient, options);
60 adapter->Initialize();
61 return absl::WrapUnique(adapter);
62 }
63
64 /* static */
CreateServerAdapter(Http2VisitorInterface & visitor,const nghttp2_option * options)65 std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateServerAdapter(
66 Http2VisitorInterface& visitor, const nghttp2_option* options) {
67 auto adapter = new NgHttp2Adapter(visitor, Perspective::kServer, options);
68 adapter->Initialize();
69 return absl::WrapUnique(adapter);
70 }
71
IsServerSession() const72 bool NgHttp2Adapter::IsServerSession() const {
73 int result = nghttp2_session_check_server_session(session_->raw_ptr());
74 QUICHE_DCHECK_EQ(perspective_ == Perspective::kServer, result > 0);
75 return result > 0;
76 }
77
ProcessBytes(absl::string_view bytes)78 int64_t NgHttp2Adapter::ProcessBytes(absl::string_view bytes) {
79 const int64_t processed_bytes = session_->ProcessBytes(bytes);
80 if (processed_bytes < 0) {
81 visitor_.OnConnectionError(ConnectionError::kParseError);
82 }
83 return processed_bytes;
84 }
85
SubmitSettings(absl::Span<const Http2Setting> settings)86 void NgHttp2Adapter::SubmitSettings(absl::Span<const Http2Setting> settings) {
87 // Submit SETTINGS, converting each Http2Setting to an nghttp2_settings_entry.
88 std::vector<nghttp2_settings_entry> nghttp2_settings;
89 absl::c_transform(settings, std::back_inserter(nghttp2_settings),
90 [](const Http2Setting& setting) {
91 return nghttp2_settings_entry{setting.id, setting.value};
92 });
93 nghttp2_submit_settings(session_->raw_ptr(), NGHTTP2_FLAG_NONE,
94 nghttp2_settings.data(), nghttp2_settings.size());
95 }
96
SubmitPriorityForStream(Http2StreamId stream_id,Http2StreamId parent_stream_id,int weight,bool exclusive)97 void NgHttp2Adapter::SubmitPriorityForStream(Http2StreamId stream_id,
98 Http2StreamId parent_stream_id,
99 int weight, bool exclusive) {
100 nghttp2_priority_spec priority_spec;
101 nghttp2_priority_spec_init(&priority_spec, parent_stream_id, weight,
102 static_cast<int>(exclusive));
103 nghttp2_submit_priority(session_->raw_ptr(), NGHTTP2_FLAG_NONE, stream_id,
104 &priority_spec);
105 }
106
SubmitPing(Http2PingId ping_id)107 void NgHttp2Adapter::SubmitPing(Http2PingId ping_id) {
108 uint8_t opaque_data[8] = {};
109 Http2PingId ping_id_to_serialize = quiche::QuicheEndian::HostToNet64(ping_id);
110 std::memcpy(opaque_data, &ping_id_to_serialize, sizeof(Http2PingId));
111 nghttp2_submit_ping(session_->raw_ptr(), NGHTTP2_FLAG_NONE, opaque_data);
112 }
113
SubmitShutdownNotice()114 void NgHttp2Adapter::SubmitShutdownNotice() {
115 nghttp2_submit_shutdown_notice(session_->raw_ptr());
116 }
117
SubmitGoAway(Http2StreamId last_accepted_stream_id,Http2ErrorCode error_code,absl::string_view opaque_data)118 void NgHttp2Adapter::SubmitGoAway(Http2StreamId last_accepted_stream_id,
119 Http2ErrorCode error_code,
120 absl::string_view opaque_data) {
121 nghttp2_submit_goaway(session_->raw_ptr(), NGHTTP2_FLAG_NONE,
122 last_accepted_stream_id,
123 static_cast<uint32_t>(error_code),
124 ToUint8Ptr(opaque_data.data()), opaque_data.size());
125 }
126
SubmitWindowUpdate(Http2StreamId stream_id,int window_increment)127 void NgHttp2Adapter::SubmitWindowUpdate(Http2StreamId stream_id,
128 int window_increment) {
129 nghttp2_submit_window_update(session_->raw_ptr(), NGHTTP2_FLAG_NONE,
130 stream_id, window_increment);
131 }
132
SubmitMetadata(Http2StreamId stream_id,size_t max_frame_size,std::unique_ptr<MetadataSource> source)133 void NgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id,
134 size_t max_frame_size,
135 std::unique_ptr<MetadataSource> source) {
136 auto wrapped_source = std::make_unique<NotifyingMetadataSource>(
137 this, stream_id, std::move(source));
138 const size_t num_frames = wrapped_source->NumFrames(max_frame_size);
139 size_t num_successes = 0;
140 for (size_t i = 1; i <= num_frames; ++i) {
141 const int result =
142 nghttp2_submit_extension(session_->raw_ptr(), kMetadataFrameType,
143 i == num_frames ? kMetadataEndFlag : 0,
144 stream_id, wrapped_source.get());
145 if (result != 0) {
146 QUICHE_LOG(DFATAL) << "Failed to submit extension frame " << i << " of "
147 << num_frames;
148 break;
149 }
150 ++num_successes;
151 }
152 if (num_successes > 0) {
153 // Finds the MetadataSourceVec for `stream_id` or inserts a new one if not
154 // present.
155 auto [it, _] = stream_metadata_.insert({stream_id, MetadataSourceVec{}});
156 it->second.push_back(std::move(wrapped_source));
157 }
158 }
159
Send()160 int NgHttp2Adapter::Send() {
161 const int result = nghttp2_session_send(session_->raw_ptr());
162 if (result != 0) {
163 QUICHE_VLOG(1) << "nghttp2_session_send returned " << result;
164 visitor_.OnConnectionError(ConnectionError::kSendError);
165 }
166 return result;
167 }
168
GetSendWindowSize() const169 int NgHttp2Adapter::GetSendWindowSize() const {
170 return session_->GetRemoteWindowSize();
171 }
172
GetStreamSendWindowSize(Http2StreamId stream_id) const173 int NgHttp2Adapter::GetStreamSendWindowSize(Http2StreamId stream_id) const {
174 return nghttp2_session_get_stream_remote_window_size(session_->raw_ptr(),
175 stream_id);
176 }
177
GetStreamReceiveWindowLimit(Http2StreamId stream_id) const178 int NgHttp2Adapter::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const {
179 return nghttp2_session_get_stream_effective_local_window_size(
180 session_->raw_ptr(), stream_id);
181 }
182
GetStreamReceiveWindowSize(Http2StreamId stream_id) const183 int NgHttp2Adapter::GetStreamReceiveWindowSize(Http2StreamId stream_id) const {
184 return nghttp2_session_get_stream_local_window_size(session_->raw_ptr(),
185 stream_id);
186 }
187
GetReceiveWindowSize() const188 int NgHttp2Adapter::GetReceiveWindowSize() const {
189 return nghttp2_session_get_local_window_size(session_->raw_ptr());
190 }
191
GetHpackEncoderDynamicTableSize() const192 int NgHttp2Adapter::GetHpackEncoderDynamicTableSize() const {
193 return nghttp2_session_get_hd_deflate_dynamic_table_size(session_->raw_ptr());
194 }
195
GetHpackDecoderDynamicTableSize() const196 int NgHttp2Adapter::GetHpackDecoderDynamicTableSize() const {
197 return nghttp2_session_get_hd_inflate_dynamic_table_size(session_->raw_ptr());
198 }
199
GetHighestReceivedStreamId() const200 Http2StreamId NgHttp2Adapter::GetHighestReceivedStreamId() const {
201 return nghttp2_session_get_last_proc_stream_id(session_->raw_ptr());
202 }
203
MarkDataConsumedForStream(Http2StreamId stream_id,size_t num_bytes)204 void NgHttp2Adapter::MarkDataConsumedForStream(Http2StreamId stream_id,
205 size_t num_bytes) {
206 int rc = session_->Consume(stream_id, num_bytes);
207 if (rc != 0) {
208 QUICHE_LOG(ERROR) << "Error " << rc << " marking " << num_bytes
209 << " bytes consumed for stream " << stream_id;
210 }
211 }
212
SubmitRst(Http2StreamId stream_id,Http2ErrorCode error_code)213 void NgHttp2Adapter::SubmitRst(Http2StreamId stream_id,
214 Http2ErrorCode error_code) {
215 int status =
216 nghttp2_submit_rst_stream(session_->raw_ptr(), NGHTTP2_FLAG_NONE,
217 stream_id, static_cast<uint32_t>(error_code));
218 if (status < 0) {
219 QUICHE_LOG(WARNING) << "Reset stream failed: " << stream_id
220 << " with status code " << status;
221 }
222 }
223
SubmitRequest(absl::Span<const Header> headers,std::unique_ptr<DataFrameSource> data_source,void * stream_user_data)224 int32_t NgHttp2Adapter::SubmitRequest(
225 absl::Span<const Header> headers,
226 std::unique_ptr<DataFrameSource> data_source, void* stream_user_data) {
227 auto nvs = GetNghttp2Nvs(headers);
228 std::unique_ptr<nghttp2_data_provider> provider =
229 MakeDataProvider(data_source.get());
230
231 int32_t stream_id =
232 nghttp2_submit_request(session_->raw_ptr(), nullptr, nvs.data(),
233 nvs.size(), provider.get(), stream_user_data);
234 sources_.emplace(stream_id, std::move(data_source));
235 QUICHE_VLOG(1) << "Submitted request with " << nvs.size()
236 << " request headers and user data " << stream_user_data
237 << "; resulted in stream " << stream_id;
238 return stream_id;
239 }
240
SubmitResponse(Http2StreamId stream_id,absl::Span<const Header> headers,std::unique_ptr<DataFrameSource> data_source)241 int NgHttp2Adapter::SubmitResponse(
242 Http2StreamId stream_id, absl::Span<const Header> headers,
243 std::unique_ptr<DataFrameSource> data_source) {
244 auto nvs = GetNghttp2Nvs(headers);
245 std::unique_ptr<nghttp2_data_provider> provider =
246 MakeDataProvider(data_source.get());
247
248 sources_.emplace(stream_id, std::move(data_source));
249
250 int result = nghttp2_submit_response(session_->raw_ptr(), stream_id,
251 nvs.data(), nvs.size(), provider.get());
252 QUICHE_VLOG(1) << "Submitted response with " << nvs.size()
253 << " response headers; result = " << result;
254 return result;
255 }
256
SubmitTrailer(Http2StreamId stream_id,absl::Span<const Header> trailers)257 int NgHttp2Adapter::SubmitTrailer(Http2StreamId stream_id,
258 absl::Span<const Header> trailers) {
259 auto nvs = GetNghttp2Nvs(trailers);
260 int result = nghttp2_submit_trailer(session_->raw_ptr(), stream_id,
261 nvs.data(), nvs.size());
262 QUICHE_VLOG(1) << "Submitted trailers with " << nvs.size()
263 << " response trailers; result = " << result;
264 return result;
265 }
266
SetStreamUserData(Http2StreamId stream_id,void * stream_user_data)267 void NgHttp2Adapter::SetStreamUserData(Http2StreamId stream_id,
268 void* stream_user_data) {
269 nghttp2_session_set_stream_user_data(session_->raw_ptr(), stream_id,
270 stream_user_data);
271 }
272
GetStreamUserData(Http2StreamId stream_id)273 void* NgHttp2Adapter::GetStreamUserData(Http2StreamId stream_id) {
274 return nghttp2_session_get_stream_user_data(session_->raw_ptr(), stream_id);
275 }
276
ResumeStream(Http2StreamId stream_id)277 bool NgHttp2Adapter::ResumeStream(Http2StreamId stream_id) {
278 return 0 == nghttp2_session_resume_data(session_->raw_ptr(), stream_id);
279 }
280
FrameNotSent(Http2StreamId stream_id,uint8_t frame_type)281 void NgHttp2Adapter::FrameNotSent(Http2StreamId stream_id, uint8_t frame_type) {
282 if (frame_type == kMetadataFrameType) {
283 RemovePendingMetadata(stream_id);
284 }
285 }
286
RemoveStream(Http2StreamId stream_id)287 void NgHttp2Adapter::RemoveStream(Http2StreamId stream_id) {
288 sources_.erase(stream_id);
289 }
290
NgHttp2Adapter(Http2VisitorInterface & visitor,Perspective perspective,const nghttp2_option * options)291 NgHttp2Adapter::NgHttp2Adapter(Http2VisitorInterface& visitor,
292 Perspective perspective,
293 const nghttp2_option* options)
294 : Http2Adapter(visitor),
295 visitor_(visitor),
296 options_(options),
297 perspective_(perspective) {}
298
~NgHttp2Adapter()299 NgHttp2Adapter::~NgHttp2Adapter() {}
300
Initialize()301 void NgHttp2Adapter::Initialize() {
302 nghttp2_option* owned_options = nullptr;
303 if (options_ == nullptr) {
304 nghttp2_option_new(&owned_options);
305 // Set some common options for compatibility.
306 nghttp2_option_set_no_closed_streams(owned_options, 1);
307 nghttp2_option_set_no_auto_window_update(owned_options, 1);
308 nghttp2_option_set_max_send_header_block_length(owned_options, 0x2000000);
309 nghttp2_option_set_max_outbound_ack(owned_options, 10000);
310 nghttp2_option_set_user_recv_extension_type(owned_options,
311 kMetadataFrameType);
312 options_ = owned_options;
313 }
314
315 session_ =
316 std::make_unique<NgHttp2Session>(perspective_, callbacks::Create(),
317 options_, static_cast<void*>(&visitor_));
318 if (owned_options != nullptr) {
319 nghttp2_option_del(owned_options);
320 }
321 options_ = nullptr;
322 }
323
RemovePendingMetadata(Http2StreamId stream_id)324 void NgHttp2Adapter::RemovePendingMetadata(Http2StreamId stream_id) {
325 auto it = stream_metadata_.find(stream_id);
326 if (it != stream_metadata_.end()) {
327 it->second.erase(it->second.begin());
328 if (it->second.empty()) {
329 stream_metadata_.erase(it);
330 }
331 }
332 }
333
334 } // namespace adapter
335 } // namespace http2
336