forked from mrsone40/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapplication.cc
More file actions
448 lines (373 loc) Β· 14 KB
/
application.cc
File metadata and controls
448 lines (373 loc) Β· 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#include "node_bob.h"
#include "uv.h"
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include <node_sockaddr-inl.h>
#include <v8.h>
#include "application.h"
#include "defs.h"
#include "endpoint.h"
#include "packet.h"
#include "session.h"
namespace node {
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::Value;
namespace quic {
struct Session::Application::StreamData final {
// The actual number of vectors in the struct, up to kMaxVectorCount.
size_t count = 0;
size_t remaining = 0;
// The stream identifier. If this is a negative value then no stream is
// identified.
int64_t id = -1;
int fin = 0;
ngtcp2_vec data[kMaxVectorCount]{};
ngtcp2_vec* buf = data;
BaseObjectPtr<Stream> stream;
};
const Session::Application_Options Session::Application_Options::kDefault = {};
Maybe<Session::Application_Options> Session::Application_Options::From(
Environment* env, Local<Value> value) {
if (value.IsEmpty() || !value->IsObject()) {
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
return Nothing<Application_Options>();
}
auto& state = BindingData::Get(env);
auto params = value.As<Object>();
Application_Options options;
#define SET(name) \
SetOption<Session::Application_Options, \
&Session::Application_Options::name>( \
env, &options, params, state.name##_string())
if (!SET(max_header_pairs) || !SET(max_header_length) ||
!SET(max_field_section_size) || !SET(qpack_max_dtable_capacity) ||
!SET(qpack_encoder_max_dtable_capacity) || !SET(qpack_blocked_streams)) {
return Nothing<Application_Options>();
}
#undef SET
return Just<Application_Options>(options);
}
Session::Application::Application(Session* session, const Options& options)
: session_(session) {}
bool Session::Application::Start() {
// By default there is nothing to do. Specific implementations may
// override to perform more actions.
return true;
}
void Session::Application::AcknowledgeStreamData(Stream* stream,
size_t datalen) {
DCHECK_NOT_NULL(stream);
stream->Acknowledge(datalen);
}
void Session::Application::BlockStream(int64_t id) {
auto stream = session().FindStream(id);
if (stream) stream->EmitBlocked();
}
bool Session::Application::CanAddHeader(size_t current_count,
size_t current_headers_length,
size_t this_header_length) {
// By default headers are not supported.
return false;
}
bool Session::Application::SendHeaders(const Stream& stream,
HeadersKind kind,
const v8::Local<v8::Array>& headers,
HeadersFlags flags) {
// By default do nothing.
return false;
}
void Session::Application::ResumeStream(int64_t id) {
// By default do nothing.
}
void Session::Application::ExtendMaxStreams(EndpointLabel label,
Direction direction,
uint64_t max_streams) {
// By default do nothing.
}
void Session::Application::ExtendMaxStreamData(Stream* stream,
uint64_t max_data) {
// By default do nothing.
}
void Session::Application::CollectSessionTicketAppData(
SessionTicket::AppData* app_data) const {
// By default do nothing.
}
SessionTicket::AppData::Status
Session::Application::ExtractSessionTicketAppData(
const SessionTicket::AppData& app_data,
SessionTicket::AppData::Source::Flag flag) {
// By default we do not have any application data to retrieve.
return flag == SessionTicket::AppData::Source::Flag::STATUS_RENEW
? SessionTicket::AppData::Status::TICKET_USE_RENEW
: SessionTicket::AppData::Status::TICKET_USE;
}
void Session::Application::SetStreamPriority(const Stream& stream,
StreamPriority priority,
StreamPriorityFlags flags) {
// By default do nothing.
}
StreamPriority Session::Application::GetStreamPriority(const Stream& stream) {
return StreamPriority::DEFAULT;
}
BaseObjectPtr<Packet> Session::Application::CreateStreamDataPacket() {
return Packet::Create(env(),
session_->endpoint_.get(),
session_->remote_address_,
ngtcp2_conn_get_max_udp_payload_size(*session_),
"stream data");
}
void Session::Application::StreamClose(Stream* stream, QuicError error) {
stream->Destroy(error);
}
void Session::Application::StreamStopSending(Stream* stream, QuicError error) {
DCHECK_NOT_NULL(stream);
stream->ReceiveStopSending(error);
}
void Session::Application::StreamReset(Stream* stream,
uint64_t final_size,
QuicError error) {
stream->ReceiveStreamReset(final_size, error);
}
void Session::Application::SendPendingData() {
PathStorage path;
BaseObjectPtr<Packet> packet;
uint8_t* pos = nullptr;
int err = 0;
size_t maxPacketCount = std::min(static_cast<size_t>(64000),
ngtcp2_conn_get_send_quantum(*session_));
size_t packetSendCount = 0;
const auto updateTimer = [&] {
ngtcp2_conn_update_pkt_tx_time(*session_, uv_hrtime());
session_->UpdateTimer();
};
const auto congestionLimited = [&](auto packet) {
auto len = pos - ngtcp2_vec(*packet).base;
// We are either congestion limited or done.
if (len) {
// Some data was serialized into the packet. We need to send it.
packet->Truncate(len);
session_->Send(std::move(packet), path);
}
updateTimer();
};
for (;;) {
ssize_t ndatalen;
StreamData stream_data;
err = GetStreamData(&stream_data);
if (err < 0) {
session_->last_error_ = QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL);
return session_->Close(Session::CloseMethod::SILENT);
}
if (!packet) {
packet = CreateStreamDataPacket();
if (!packet) {
session_->last_error_ = QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL);
return session_->Close(Session::CloseMethod::SILENT);
}
pos = ngtcp2_vec(*packet).base;
}
ssize_t nwrite = WriteVStream(&path, pos, &ndatalen, stream_data);
if (nwrite <= 0) {
switch (nwrite) {
case 0:
if (stream_data.id >= 0) ResumeStream(stream_data.id);
return congestionLimited(std::move(packet));
case NGTCP2_ERR_STREAM_DATA_BLOCKED: {
session().StreamDataBlocked(stream_data.id);
if (session().max_data_left() == 0) {
if (stream_data.id >= 0) ResumeStream(stream_data.id);
return congestionLimited(std::move(packet));
}
CHECK_LE(ndatalen, 0);
continue;
}
case NGTCP2_ERR_STREAM_SHUT_WR: {
// Indicates that the writable side of the stream has been closed
// locally or the stream is being reset. In either case, we can't send
// any stream data!
CHECK_GE(stream_data.id, 0);
// We need to notify the stream that the writable side has been closed
// and no more outbound data can be sent.
CHECK_LE(ndatalen, 0);
auto stream = session_->FindStream(stream_data.id);
if (stream) stream->EndWritable();
continue;
}
case NGTCP2_ERR_WRITE_MORE: {
CHECK_GT(ndatalen, 0);
if (!StreamCommit(&stream_data, ndatalen)) return session_->Close();
pos += ndatalen;
continue;
}
}
packet->Done(UV_ECANCELED);
session_->last_error_ = QuicError::ForNgtcp2Error(nwrite);
return session_->Close(Session::CloseMethod::SILENT);
}
pos += nwrite;
if (ndatalen > 0 && !StreamCommit(&stream_data, ndatalen)) {
// Since we are closing the session here, we don't worry about updating
// the pkt tx time. The failed StreamCommit should have updated the
// last_error_ appropriately.
packet->Done(UV_ECANCELED);
return session_->Close(Session::CloseMethod::SILENT);
}
if (stream_data.id >= 0 && ndatalen < 0) ResumeStream(stream_data.id);
packet->Truncate(nwrite);
session_->Send(std::move(packet), path);
pos = nullptr;
if (++packetSendCount == maxPacketCount) {
break;
}
}
updateTimer();
}
ssize_t Session::Application::WriteVStream(PathStorage* path,
uint8_t* buf,
ssize_t* ndatalen,
const StreamData& stream_data) {
CHECK_LE(stream_data.count, kMaxVectorCount);
uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_NONE;
if (stream_data.remaining > 0) flags |= NGTCP2_WRITE_STREAM_FLAG_MORE;
if (stream_data.fin) flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
ssize_t ret =
ngtcp2_conn_writev_stream(*session_,
&path->path,
nullptr,
buf,
ngtcp2_conn_get_max_udp_payload_size(*session_),
ndatalen,
flags,
stream_data.id,
stream_data.buf,
stream_data.count,
uv_hrtime());
return ret;
}
// The DefaultApplication is the default implementation of Session::Application
// that is used for all unrecognized ALPN identifiers.
class DefaultApplication final : public Session::Application {
public:
// Marked NOLINT because the cpp linter gets confused about this using
// statement not being sorted with the using v8 statements at the top
// of the namespace.
using Application::Application; // NOLINT
bool ReceiveStreamData(Stream* stream,
const uint8_t* data,
size_t datalen,
Stream::ReceiveDataFlags flags) override {
DCHECK_NOT_NULL(stream);
if (!stream->is_destroyed()) stream->ReceiveData(data, datalen, flags);
return true;
}
int GetStreamData(StreamData* stream_data) override {
DCHECK_NOT_NULL(stream_data);
// If the queue is empty, there aren't any streams with data yet
if (stream_queue_.IsEmpty()) return 0;
const auto get_length = [](auto vec, size_t count) {
CHECK_NOT_NULL(vec);
size_t len = 0;
for (size_t n = 0; n < count; n++) len += vec[n].len;
return len;
};
Stream* stream = stream_queue_.PopFront();
CHECK_NOT_NULL(stream);
stream_data->stream.reset(stream);
stream_data->id = stream->id();
auto next =
[&](int status, const ngtcp2_vec* data, size_t count, bob::Done done) {
switch (status) {
case bob::Status::STATUS_BLOCK:
// Fall through
case bob::Status::STATUS_WAIT:
return;
case bob::Status::STATUS_EOS:
stream_data->fin = 1;
}
stream_data->count = count;
if (count > 0) {
stream->Schedule(&stream_queue_);
stream_data->remaining = get_length(data, count);
} else {
stream_data->remaining = 0;
}
// Not calling done here because we defer committing
// the data until after we're sure it's written.
};
if (LIKELY(!stream->is_eos())) {
int ret = stream->Pull(std::move(next),
bob::Options::OPTIONS_SYNC,
stream_data->data,
arraysize(stream_data->data),
kMaxVectorCount);
if (ret == bob::Status::STATUS_EOS) {
stream_data->fin = 1;
}
} else {
stream_data->fin = 1;
}
return 0;
}
void ResumeStream(int64_t id) override { ScheduleStream(id); }
bool ShouldSetFin(const StreamData& stream_data) override {
auto const is_empty = [](auto vec, size_t cnt) {
size_t i;
for (i = 0; i < cnt && vec[i].len == 0; ++i) {
}
return i == cnt;
};
return stream_data.stream && is_empty(stream_data.buf, stream_data.count);
}
bool StreamCommit(StreamData* stream_data, size_t datalen) override {
DCHECK_NOT_NULL(stream_data);
const auto consume = [](ngtcp2_vec** pvec, size_t* pcnt, size_t len) {
ngtcp2_vec* v = *pvec;
size_t cnt = *pcnt;
for (; cnt > 0; --cnt, ++v) {
if (v->len > len) {
v->len -= len;
v->base += len;
break;
}
len -= v->len;
}
*pvec = v;
*pcnt = cnt;
};
CHECK(stream_data->stream);
stream_data->remaining -= datalen;
consume(&stream_data->buf, &stream_data->count, datalen);
stream_data->stream->Commit(datalen);
return true;
}
SET_SELF_SIZE(DefaultApplication)
SET_MEMORY_INFO_NAME(DefaultApplication)
SET_NO_MEMORY_INFO()
private:
void ScheduleStream(int64_t id) {
auto stream = session().FindStream(id);
if (stream && !stream->is_destroyed()) {
stream->Schedule(&stream_queue_);
}
}
void UnscheduleStream(int64_t id) {
auto stream = session().FindStream(id);
if (stream && !stream->is_destroyed()) stream->Unschedule();
}
Stream::Queue stream_queue_;
};
std::unique_ptr<Session::Application> Session::select_application() {
// if (config.options.crypto_options.alpn == NGHTTP3_ALPN_H3)
// return std::make_unique<Http3>(session,
// config.options.application_options);
// In the future, we may end up supporting additional QUIC protocols. As they
// are added, extend the cases here to create and return them.
return std::make_unique<DefaultApplication>(
this, config_.options.application_options);
}
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC