1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2019 nghttp2 contributors
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 #include "h2load_http3_session.h"
26 
27 #include <iostream>
28 
29 #include <ngtcp2/ngtcp2.h>
30 
31 #include "h2load.h"
32 
33 namespace h2load {
34 
Http3Session(Client * client)35 Http3Session::Http3Session(Client *client)
36     : client_(client), conn_(nullptr), npending_request_(0), reqidx_(0) {}
37 
~Http3Session()38 Http3Session::~Http3Session() { nghttp3_conn_del(conn_); }
39 
on_connect()40 void Http3Session::on_connect() {}
41 
submit_request()42 int Http3Session::submit_request() {
43   if (npending_request_) {
44     ++npending_request_;
45     return 0;
46   }
47 
48   auto config = client_->worker->config;
49   reqidx_ = client_->reqidx;
50 
51   if (++client_->reqidx == config->nva.size()) {
52     client_->reqidx = 0;
53   }
54 
55   auto stream_id = submit_request_internal();
56   if (stream_id < 0) {
57     if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
58       ++npending_request_;
59       return 0;
60     }
61     return -1;
62   }
63 
64   return 0;
65 }
66 
67 namespace {
read_data(nghttp3_conn * conn,int64_t stream_id,nghttp3_vec * vec,size_t veccnt,uint32_t * pflags,void * user_data,void * stream_user_data)68 nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
69                         size_t veccnt, uint32_t *pflags, void *user_data,
70                         void *stream_user_data) {
71   auto s = static_cast<Http3Session *>(user_data);
72 
73   s->read_data(vec, veccnt, pflags);
74 
75   return 1;
76 }
77 } // namespace
78 
read_data(nghttp3_vec * vec,size_t veccnt,uint32_t * pflags)79 void Http3Session::read_data(nghttp3_vec *vec, size_t veccnt,
80                              uint32_t *pflags) {
81   assert(veccnt > 0);
82 
83   auto config = client_->worker->config;
84 
85   vec[0].base = config->data;
86   vec[0].len = config->data_length;
87   *pflags |= NGHTTP3_DATA_FLAG_EOF;
88 }
89 
submit_request_internal()90 int64_t Http3Session::submit_request_internal() {
91   int rv;
92   int64_t stream_id;
93 
94   auto config = client_->worker->config;
95   auto &nva = config->nva[reqidx_];
96 
97   rv = ngtcp2_conn_open_bidi_stream(client_->quic.conn, &stream_id, nullptr);
98   if (rv != 0) {
99     return rv;
100   }
101 
102   nghttp3_data_reader dr{};
103   dr.read_data = h2load::read_data;
104 
105   rv = nghttp3_conn_submit_request(
106       conn_, stream_id, reinterpret_cast<nghttp3_nv *>(nva.data()), nva.size(),
107       config->data_fd == -1 ? nullptr : &dr, nullptr);
108   if (rv != 0) {
109     return rv;
110   }
111 
112   client_->on_request(stream_id);
113   auto req_stat = client_->get_req_stat(stream_id);
114   assert(req_stat);
115   client_->record_request_time(req_stat);
116 
117   return stream_id;
118 }
119 
on_read(const uint8_t * data,size_t len)120 int Http3Session::on_read(const uint8_t *data, size_t len) { return -1; }
121 
on_write()122 int Http3Session::on_write() { return -1; }
123 
terminate()124 void Http3Session::terminate() {}
125 
max_concurrent_streams()126 size_t Http3Session::max_concurrent_streams() {
127   return (size_t)client_->worker->config->max_concurrent_streams;
128 }
129 
130 namespace {
stream_close(nghttp3_conn * conn,int64_t stream_id,uint64_t app_error_code,void * user_data,void * stream_user_data)131 int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
132                  void *user_data, void *stream_user_data) {
133   auto s = static_cast<Http3Session *>(user_data);
134   if (s->stream_close(stream_id, app_error_code) != 0) {
135     return NGHTTP3_ERR_CALLBACK_FAILURE;
136   }
137   return 0;
138 }
139 } // namespace
140 
stream_close(int64_t stream_id,uint64_t app_error_code)141 int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) {
142   if (!ngtcp2_is_bidi_stream(stream_id)) {
143     assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
144     ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
145   }
146   client_->on_stream_close(stream_id, app_error_code == NGHTTP3_H3_NO_ERROR);
147   return 0;
148 }
149 
150 namespace {
recv_data(nghttp3_conn * conn,int64_t stream_id,const uint8_t * data,size_t datalen,void * user_data,void * stream_user_data)151 int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
152               size_t datalen, void *user_data, void *stream_user_data) {
153   auto s = static_cast<Http3Session *>(user_data);
154   s->recv_data(stream_id, data, datalen);
155   return 0;
156 }
157 } // namespace
158 
recv_data(int64_t stream_id,const uint8_t * data,size_t datalen)159 void Http3Session::recv_data(int64_t stream_id, const uint8_t *data,
160                              size_t datalen) {
161   client_->record_ttfb();
162   client_->worker->stats.bytes_body += datalen;
163   consume(stream_id, datalen);
164 }
165 
166 namespace {
deferred_consume(nghttp3_conn * conn,int64_t stream_id,size_t nconsumed,void * user_data,void * stream_user_data)167 int deferred_consume(nghttp3_conn *conn, int64_t stream_id, size_t nconsumed,
168                      void *user_data, void *stream_user_data) {
169   auto s = static_cast<Http3Session *>(user_data);
170   s->consume(stream_id, nconsumed);
171   return 0;
172 }
173 } // namespace
174 
consume(int64_t stream_id,size_t nconsumed)175 void Http3Session::consume(int64_t stream_id, size_t nconsumed) {
176   ngtcp2_conn_extend_max_stream_offset(client_->quic.conn, stream_id,
177                                        nconsumed);
178   ngtcp2_conn_extend_max_offset(client_->quic.conn, nconsumed);
179 }
180 
181 namespace {
begin_headers(nghttp3_conn * conn,int64_t stream_id,void * user_data,void * stream_user_data)182 int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data,
183                   void *stream_user_data) {
184   auto s = static_cast<Http3Session *>(user_data);
185   s->begin_headers(stream_id);
186   return 0;
187 }
188 } // namespace
189 
begin_headers(int64_t stream_id)190 void Http3Session::begin_headers(int64_t stream_id) {
191   auto payloadlen = nghttp3_conn_get_frame_payload_left(conn_, stream_id);
192   assert(payloadlen > 0);
193 
194   client_->worker->stats.bytes_head += payloadlen;
195 }
196 
197 namespace {
recv_header(nghttp3_conn * conn,int64_t stream_id,int32_t token,nghttp3_rcbuf * name,nghttp3_rcbuf * value,uint8_t flags,void * user_data,void * stream_user_data)198 int recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token,
199                 nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags,
200                 void *user_data, void *stream_user_data) {
201   auto s = static_cast<Http3Session *>(user_data);
202   auto k = nghttp3_rcbuf_get_buf(name);
203   auto v = nghttp3_rcbuf_get_buf(value);
204   s->recv_header(stream_id, &k, &v);
205   return 0;
206 }
207 } // namespace
208 
recv_header(int64_t stream_id,const nghttp3_vec * name,const nghttp3_vec * value)209 void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name,
210                                const nghttp3_vec *value) {
211   client_->on_header(stream_id, name->base, name->len, value->base, value->len);
212   client_->worker->stats.bytes_head_decomp += name->len + value->len;
213 }
214 
215 namespace {
send_stop_sending(nghttp3_conn * conn,int64_t stream_id,uint64_t app_error_code,void * user_data,void * stream_user_data)216 int send_stop_sending(nghttp3_conn *conn, int64_t stream_id,
217                       uint64_t app_error_code, void *user_data,
218                       void *stream_user_data) {
219   auto s = static_cast<Http3Session *>(user_data);
220   if (s->send_stop_sending(stream_id, app_error_code) != 0) {
221     return NGHTTP3_ERR_CALLBACK_FAILURE;
222   }
223   return 0;
224 }
225 } // namespace
226 
send_stop_sending(int64_t stream_id,uint64_t app_error_code)227 int Http3Session::send_stop_sending(int64_t stream_id,
228                                     uint64_t app_error_code) {
229   auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, stream_id,
230                                              app_error_code);
231   if (rv != 0) {
232     std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
233               << std::endl;
234     return -1;
235   }
236   return 0;
237 }
238 
close_stream(int64_t stream_id,uint64_t app_error_code)239 int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) {
240   auto rv = nghttp3_conn_close_stream(conn_, stream_id, app_error_code);
241   switch (rv) {
242   case 0:
243     return 0;
244   case NGHTTP3_ERR_STREAM_NOT_FOUND:
245     if (!ngtcp2_is_bidi_stream(stream_id)) {
246       assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
247       ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
248     }
249     return 0;
250   default:
251     return -1;
252   }
253 }
254 
shutdown_stream_read(int64_t stream_id)255 int Http3Session::shutdown_stream_read(int64_t stream_id) {
256   auto rv = nghttp3_conn_shutdown_stream_read(conn_, stream_id);
257   if (rv != 0) {
258     return -1;
259   }
260   return 0;
261 }
262 
extend_max_local_streams()263 int Http3Session::extend_max_local_streams() {
264   auto config = client_->worker->config;
265 
266   for (; npending_request_; --npending_request_) {
267     auto stream_id = submit_request_internal();
268     if (stream_id < 0) {
269       if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
270         return 0;
271       }
272       return -1;
273     }
274 
275     if (++reqidx_ == config->nva.size()) {
276       reqidx_ = 0;
277     }
278   }
279 
280   return 0;
281 }
282 
init_conn()283 int Http3Session::init_conn() {
284   int rv;
285 
286   assert(conn_ == nullptr);
287 
288   if (ngtcp2_conn_get_max_local_streams_uni(client_->quic.conn) < 3) {
289     return -1;
290   }
291 
292   nghttp3_callbacks callbacks{
293       nullptr, // acked_stream_data
294       h2load::stream_close,
295       h2load::recv_data,
296       h2load::deferred_consume,
297       h2load::begin_headers,
298       h2load::recv_header,
299       nullptr, // end_headers
300       nullptr, // begin_trailers
301       h2load::recv_header,
302       nullptr, // end_trailers
303       h2load::send_stop_sending,
304   };
305 
306   auto config = client_->worker->config;
307 
308   nghttp3_settings settings;
309   nghttp3_settings_default(&settings);
310   settings.qpack_max_table_capacity = config->header_table_size;
311   settings.qpack_blocked_streams = 100;
312 
313   auto mem = nghttp3_mem_default();
314 
315   rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this);
316   if (rv != 0) {
317     std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv)
318               << std::endl;
319     return -1;
320   }
321 
322   int64_t ctrl_stream_id;
323 
324   rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, NULL);
325   if (rv != 0) {
326     std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
327               << std::endl;
328     return -1;
329   }
330 
331   rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id);
332   if (rv != 0) {
333     std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
334               << std::endl;
335     return -1;
336   }
337 
338   int64_t qpack_enc_stream_id, qpack_dec_stream_id;
339 
340   rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id,
341                                    NULL);
342   if (rv != 0) {
343     std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
344               << std::endl;
345     return -1;
346   }
347 
348   rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id,
349                                    NULL);
350   if (rv != 0) {
351     std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
352               << std::endl;
353     return -1;
354   }
355 
356   rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id,
357                                        qpack_dec_stream_id);
358   if (rv != 0) {
359     std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
360               << std::endl;
361     return -1;
362   }
363 
364   return 0;
365 }
366 
read_stream(uint32_t flags,int64_t stream_id,const uint8_t * data,size_t datalen)367 ssize_t Http3Session::read_stream(uint32_t flags, int64_t stream_id,
368                                   const uint8_t *data, size_t datalen) {
369   auto nconsumed = nghttp3_conn_read_stream(
370       conn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
371   if (nconsumed < 0) {
372     std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
373               << std::endl;
374     client_->quic.last_error = quic::err_application(nconsumed);
375     return -1;
376   }
377   return nconsumed;
378 }
379 
write_stream(int64_t & stream_id,int & fin,nghttp3_vec * vec,size_t veccnt)380 ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin,
381                                    nghttp3_vec *vec, size_t veccnt) {
382   auto sveccnt =
383       nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt);
384   if (sveccnt < 0) {
385     client_->quic.last_error = quic::err_application(sveccnt);
386     return -1;
387   }
388   return sveccnt;
389 }
390 
block_stream(int64_t stream_id)391 int Http3Session::block_stream(int64_t stream_id) {
392   auto rv = nghttp3_conn_block_stream(conn_, stream_id);
393   if (rv != 0) {
394     client_->quic.last_error = quic::err_application(rv);
395     return -1;
396   }
397   return 0;
398 }
399 
shutdown_stream_write(int64_t stream_id)400 int Http3Session::shutdown_stream_write(int64_t stream_id) {
401   auto rv = nghttp3_conn_shutdown_stream_write(conn_, stream_id);
402   if (rv != 0) {
403     client_->quic.last_error = quic::err_application(rv);
404     return -1;
405   }
406   return 0;
407 }
408 
add_write_offset(int64_t stream_id,size_t ndatalen)409 int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) {
410   auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen);
411   if (rv != 0) {
412     client_->quic.last_error = quic::err_application(rv);
413     return -1;
414   }
415   return 0;
416 }
417 
add_ack_offset(int64_t stream_id,size_t datalen)418 int Http3Session::add_ack_offset(int64_t stream_id, size_t datalen) {
419   auto rv = nghttp3_conn_add_ack_offset(conn_, stream_id, datalen);
420   if (rv != 0) {
421     client_->quic.last_error = quic::err_application(rv);
422     return -1;
423   }
424   return 0;
425 }
426 
427 } // namespace h2load
428