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