%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/quic/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/quic/application.cc |
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include "application.h"
#include <async_wrap-inl.h>
#include <debug_utils-inl.h>
#include <node_bob.h>
#include <node_sockaddr-inl.h>
#include <uv.h>
#include <v8.h>
#include "defs.h"
#include "endpoint.h"
#include "http3.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 {
// ============================================================================
// Session::Application_Options
const Session::Application_Options Session::Application_Options::kDefault = {};
Session::Application_Options::operator const nghttp3_settings() const {
// In theory, Application_Options might contain options for more than just
// HTTP/3. Here we extract only the properties that are relevant to HTTP/3.
return nghttp3_settings{
max_field_section_size,
static_cast<size_t>(qpack_max_dtable_capacity),
static_cast<size_t>(qpack_encoder_max_dtable_capacity),
static_cast<size_t>(qpack_blocked_streams),
enable_connect_protocol,
enable_datagrams,
};
}
std::string Session::Application_Options::ToString() const {
DebugIndentScope indent;
auto prefix = indent.Prefix();
std::string res("{");
res += prefix + "max header pairs: " + std::to_string(max_header_pairs);
res += prefix + "max header length: " + std::to_string(max_header_length);
res += prefix +
"max field section size: " + std::to_string(max_field_section_size);
res += prefix + "qpack max dtable capacity: " +
std::to_string(qpack_max_dtable_capacity);
res += prefix + "qpack encoder max dtable capacity: " +
std::to_string(qpack_encoder_max_dtable_capacity);
res += prefix +
"qpack blocked streams: " + std::to_string(qpack_blocked_streams);
res += prefix + "enable connect protocol: " +
(enable_connect_protocol ? std::string("yes") : std::string("no"));
res += prefix + "enable datagrams: " +
(enable_datagrams ? std::string("yes") : std::string("no"));
res += indent.Close();
return res;
}
Maybe<Session::Application_Options> Session::Application_Options::From(
Environment* env, Local<Value> value) {
if (value.IsEmpty() || (!value->IsUndefined() && !value->IsObject())) {
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
return Nothing<Application_Options>();
}
Application_Options options;
auto& state = BindingData::Get(env);
if (value->IsUndefined()) {
return Just<Application_Options>(options);
}
auto params = value.As<Object>();
#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) ||
!SET(enable_connect_protocol) || !SET(enable_datagrams)) {
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.
Debug(session_, "Session application started");
return true;
}
void Session::Application::AcknowledgeStreamData(Stream* stream,
size_t datalen) {
Debug(session_,
"Application acknowledging stream %" PRIi64 " data: %zu",
stream->id(),
datalen);
DCHECK_NOT_NULL(stream);
stream->Acknowledge(datalen);
}
void Session::Application::BlockStream(int64_t id) {
Debug(session_, "Application blocking stream %" PRIi64, 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.
Debug(session_, "Application cannot add header");
return false;
}
bool Session::Application::SendHeaders(const Stream& stream,
HeadersKind kind,
const v8::Local<v8::Array>& headers,
HeadersFlags flags) {
// By default do nothing.
Debug(session_, "Application cannot send headers");
return false;
}
void Session::Application::ResumeStream(int64_t id) {
Debug(session_, "Application resuming stream %" PRIi64, id);
// By default do nothing.
}
void Session::Application::ExtendMaxStreams(EndpointLabel label,
Direction direction,
uint64_t max_streams) {
Debug(session_, "Application extending max streams");
// By default do nothing.
}
void Session::Application::ExtendMaxStreamData(Stream* stream,
uint64_t max_data) {
Debug(session_, "Application extending max stream data");
// By default do nothing.
}
void Session::Application::CollectSessionTicketAppData(
SessionTicket::AppData* app_data) const {
Debug(session_, "Application collecting session ticket app data");
// By default do nothing.
}
SessionTicket::AppData::Status
Session::Application::ExtractSessionTicketAppData(
const SessionTicket::AppData& app_data,
SessionTicket::AppData::Source::Flag flag) {
Debug(session_, "Application extracting session ticket app data");
// 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) {
Debug(
session_, "Application setting stream %" PRIi64 " priority", stream.id());
// By default do nothing.
}
StreamPriority Session::Application::GetStreamPriority(const Stream& stream) {
return StreamPriority::DEFAULT;
}
Packet* Session::Application::CreateStreamDataPacket() {
return Packet::Create(env(),
session_->endpoint_.get(),
session_->remote_address_,
ngtcp2_conn_get_max_tx_udp_payload_size(*session_),
"stream data");
}
void Session::Application::StreamClose(Stream* stream, QuicError error) {
Debug(session_,
"Application closing stream %" PRIi64 " with error %s",
stream->id(),
error);
stream->Destroy(error);
}
void Session::Application::StreamStopSending(Stream* stream, QuicError error) {
Debug(session_,
"Application stopping sending on stream %" PRIi64 " with error %s",
stream->id(),
error);
DCHECK_NOT_NULL(stream);
stream->ReceiveStopSending(error);
}
void Session::Application::StreamReset(Stream* stream,
uint64_t final_size,
QuicError error) {
Debug(session_,
"Application resetting stream %" PRIi64 " with error %s",
stream->id(),
error);
stream->ReceiveStreamReset(final_size, error);
}
void Session::Application::SendPendingData() {
Debug(session_, "Application sending pending data");
PathStorage path;
Packet* packet = nullptr;
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 = [&] {
Debug(session_, "Application updating the session timer");
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 == nullptr) {
packet = CreateStreamDataPacket();
if (packet == nullptr) {
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_tx_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 {
Debug(&session(), "Default application receiving stream data");
DCHECK_NOT_NULL(stream);
if (!stream->is_destroyed()) stream->ReceiveData(data, datalen, flags);
return true;
}
int GetStreamData(StreamData* stream_data) override {
Debug(&session(), "Default application getting stream data");
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 {
Debug(&session(), "Default application resuming stream %" PRIi64, id);
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 {
Debug(&session(), "Default application committing stream data");
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) {
Debug(&session(), "Default application scheduling stream %" PRIi64, id);
auto stream = session().FindStream(id);
if (stream && !stream->is_destroyed()) {
stream->Schedule(&stream_queue_);
}
}
void UnscheduleStream(int64_t id) {
Debug(&session(), "Default application unscheduling stream %" PRIi64, 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() {
// In the future, we may end up supporting additional QUIC protocols. As they
// are added, extend the cases here to create and return them.
if (config_.options.tls_options.alpn == NGHTTP3_ALPN_H3) {
Debug(this, "Selecting HTTP/3 application");
return createHttp3Application(this, config_.options.application_options);
}
Debug(this, "Selecting default application");
return std::make_unique<DefaultApplication>(
this, config_.options.application_options);
}
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC