mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
PR-URL: https://github.com/nodejs/node/pull/55155 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
838 lines
30 KiB
C++
838 lines
30 KiB
C++
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
|
|
|
#include "http3.h"
|
|
#include <async_wrap-inl.h>
|
|
#include <base_object-inl.h>
|
|
#include <debug_utils-inl.h>
|
|
#include <env-inl.h>
|
|
#include <memory_tracker-inl.h>
|
|
#include <nghttp3/nghttp3.h>
|
|
#include <ngtcp2/ngtcp2.h>
|
|
#include <node_http_common-inl.h>
|
|
#include <node_sockaddr-inl.h>
|
|
#include <util-inl.h>
|
|
#include "application.h"
|
|
#include "bindingdata.h"
|
|
#include "defs.h"
|
|
#include "session.h"
|
|
#include "sessionticket.h"
|
|
|
|
namespace node::quic {
|
|
namespace {
|
|
|
|
struct Http3HeadersTraits {
|
|
typedef nghttp3_nv nv_t;
|
|
};
|
|
|
|
struct Http3RcBufferPointerTraits {
|
|
typedef nghttp3_rcbuf rcbuf_t;
|
|
typedef nghttp3_vec vector_t;
|
|
|
|
static void inc(rcbuf_t* buf) {
|
|
CHECK_NOT_NULL(buf);
|
|
nghttp3_rcbuf_incref(buf);
|
|
}
|
|
static void dec(rcbuf_t* buf) {
|
|
CHECK_NOT_NULL(buf);
|
|
nghttp3_rcbuf_decref(buf);
|
|
}
|
|
static vector_t get_vec(rcbuf_t* buf) {
|
|
CHECK_NOT_NULL(buf);
|
|
return nghttp3_rcbuf_get_buf(buf);
|
|
}
|
|
static bool is_static(const rcbuf_t* buf) {
|
|
CHECK_NOT_NULL(buf);
|
|
return nghttp3_rcbuf_is_static(buf);
|
|
}
|
|
};
|
|
|
|
using Http3ConnectionPointer = DeleteFnPtr<nghttp3_conn, nghttp3_conn_del>;
|
|
using Http3Headers = NgHeaders<Http3HeadersTraits>;
|
|
using Http3RcBufferPointer = NgRcBufPointer<Http3RcBufferPointerTraits>;
|
|
|
|
struct Http3HeaderTraits {
|
|
typedef Http3RcBufferPointer rcbufferpointer_t;
|
|
typedef BindingData allocator_t;
|
|
|
|
static const char* ToHttpHeaderName(int32_t token) {
|
|
switch (token) {
|
|
case -1:
|
|
return nullptr;
|
|
#define V(key, name) \
|
|
case NGHTTP3_QPACK_TOKEN__##key: \
|
|
return name;
|
|
HTTP_SPECIAL_HEADERS(V)
|
|
#undef V
|
|
#define V(key, name) \
|
|
case NGHTTP3_QPACK_TOKEN_##key: \
|
|
return name;
|
|
HTTP_REGULAR_HEADERS(V)
|
|
#undef V
|
|
}
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
using Http3Header = NgHeader<Http3HeaderTraits>;
|
|
|
|
// Implements the low-level HTTP/3 Application semantics.
|
|
class Http3Application final : public Session::Application {
|
|
public:
|
|
Http3Application(Session* session,
|
|
const Session::Application_Options& options)
|
|
: Application(session, options),
|
|
allocator_(BindingData::Get(env())),
|
|
options_(options),
|
|
conn_(InitializeConnection()) {
|
|
session->set_priority_supported();
|
|
}
|
|
|
|
bool Start() override {
|
|
CHECK(!started_);
|
|
started_ = true;
|
|
Debug(&session(), "Starting HTTP/3 application.");
|
|
auto params = ngtcp2_conn_get_remote_transport_params(session());
|
|
if (params == nullptr) {
|
|
// The params are not available yet. Cannot start.
|
|
Debug(&session(),
|
|
"Cannot start HTTP/3 application yet. No remote transport params");
|
|
return false;
|
|
}
|
|
|
|
if (params->initial_max_streams_uni < 3) {
|
|
// If the initial max unidirectional stream limit is not at least three,
|
|
// we cannot actually use it since we need to create the control streams.
|
|
Debug(&session(),
|
|
"Cannot start HTTP/3 application. Initial max "
|
|
"unidirectional streams is too low");
|
|
return false;
|
|
}
|
|
|
|
if (session().is_server()) {
|
|
nghttp3_conn_set_max_client_streams_bidi(
|
|
*this, params->initial_max_streams_bidi);
|
|
}
|
|
|
|
return CreateAndBindControlStreams();
|
|
}
|
|
|
|
bool ReceiveStreamData(Stream* stream,
|
|
const uint8_t* data,
|
|
size_t datalen,
|
|
Stream::ReceiveDataFlags flags) override {
|
|
Debug(&session(), "HTTP/3 application received %zu bytes of data", datalen);
|
|
ssize_t nread = nghttp3_conn_read_stream(
|
|
*this, stream->id(), data, datalen, flags.fin ? 1 : 0);
|
|
|
|
if (nread < 0) {
|
|
Debug(&session(),
|
|
"HTTP/3 application failed to read stream data: %s",
|
|
nghttp3_strerror(nread));
|
|
return false;
|
|
}
|
|
|
|
Debug(&session(),
|
|
"Extending stream and connection offset by %zd bytes",
|
|
nread);
|
|
session().ExtendStreamOffset(stream->id(), nread);
|
|
session().ExtendOffset(nread);
|
|
|
|
return true;
|
|
}
|
|
|
|
void AcknowledgeStreamData(Stream* stream, size_t datalen) override {
|
|
Debug(&session(),
|
|
"HTTP/3 application received acknowledgement for %zu bytes of data",
|
|
datalen);
|
|
CHECK_EQ(nghttp3_conn_add_ack_offset(*this, stream->id(), datalen), 0);
|
|
}
|
|
|
|
bool CanAddHeader(size_t current_count,
|
|
size_t current_headers_length,
|
|
size_t this_header_length) override {
|
|
// We cannot add the header if we've either reached
|
|
// * the max number of header pairs or
|
|
// * the max number of header bytes
|
|
bool answer = (current_count < options_.max_header_pairs) &&
|
|
(current_headers_length + this_header_length) <=
|
|
options_.max_header_length;
|
|
IF_QUIC_DEBUG(env()) {
|
|
if (answer) {
|
|
Debug(&session(), "HTTP/3 application can add header");
|
|
} else {
|
|
Debug(&session(), "HTTP/3 application cannot add header");
|
|
}
|
|
}
|
|
return answer;
|
|
}
|
|
|
|
void BlockStream(int64_t id) override {
|
|
nghttp3_conn_block_stream(*this, id);
|
|
Application::BlockStream(id);
|
|
}
|
|
|
|
void ResumeStream(int64_t id) override {
|
|
nghttp3_conn_resume_stream(*this, id);
|
|
Application::ResumeStream(id);
|
|
}
|
|
|
|
void ExtendMaxStreams(EndpointLabel label,
|
|
Direction direction,
|
|
uint64_t max_streams) override {
|
|
switch (label) {
|
|
case EndpointLabel::LOCAL:
|
|
return;
|
|
case EndpointLabel::REMOTE: {
|
|
switch (direction) {
|
|
case Direction::BIDIRECTIONAL: {
|
|
Debug(&session(),
|
|
"HTTP/3 application extending max bidi streams to %" PRIu64,
|
|
max_streams);
|
|
ngtcp2_conn_extend_max_streams_bidi(
|
|
session(), static_cast<size_t>(max_streams));
|
|
break;
|
|
}
|
|
case Direction::UNIDIRECTIONAL: {
|
|
Debug(&session(),
|
|
"HTTP/3 application extending max uni streams to %" PRIu64,
|
|
max_streams);
|
|
ngtcp2_conn_extend_max_streams_uni(
|
|
session(), static_cast<size_t>(max_streams));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExtendMaxStreamData(Stream* stream, uint64_t max_data) override {
|
|
Debug(&session(),
|
|
"HTTP/3 application extending max stream data to %" PRIu64,
|
|
max_data);
|
|
nghttp3_conn_unblock_stream(*this, stream->id());
|
|
}
|
|
|
|
void CollectSessionTicketAppData(
|
|
SessionTicket::AppData* app_data) const override {
|
|
// TODO(@jasnell): There's currently nothing to store but there may be
|
|
// later.
|
|
}
|
|
|
|
SessionTicket::AppData::Status ExtractSessionTicketAppData(
|
|
const SessionTicket::AppData& app_data,
|
|
SessionTicket::AppData::Source::Flag flag) override {
|
|
// There's currently nothing stored here but we might do so later.
|
|
return flag == SessionTicket::AppData::Source::Flag::STATUS_RENEW
|
|
? SessionTicket::AppData::Status::TICKET_USE_RENEW
|
|
: SessionTicket::AppData::Status::TICKET_USE;
|
|
}
|
|
|
|
void StreamClose(Stream* stream, QuicError error = QuicError()) override {
|
|
Debug(
|
|
&session(), "HTTP/3 application closing stream %" PRIi64, stream->id());
|
|
uint64_t code = NGHTTP3_H3_NO_ERROR;
|
|
if (error) {
|
|
CHECK_EQ(error.type(), QuicError::Type::APPLICATION);
|
|
code = error.code();
|
|
}
|
|
|
|
int rv = nghttp3_conn_close_stream(*this, stream->id(), code);
|
|
// If the call is successful, Http3Application::OnStreamClose callback will
|
|
// be invoked when the stream is ready to be closed. We'll handle destroying
|
|
// the actual Stream object there.
|
|
if (rv == 0) return;
|
|
|
|
if (rv == NGHTTP3_ERR_STREAM_NOT_FOUND) {
|
|
ExtendMaxStreams(EndpointLabel::REMOTE, stream->direction(), 1);
|
|
return;
|
|
}
|
|
|
|
session().SetLastError(
|
|
QuicError::ForApplication(nghttp3_err_infer_quic_app_error_code(rv)));
|
|
session().Close();
|
|
}
|
|
|
|
void StreamReset(Stream* stream,
|
|
uint64_t final_size,
|
|
QuicError error) override {
|
|
// We are shutting down the readable side of the local stream here.
|
|
Debug(&session(),
|
|
"HTTP/3 application resetting stream %" PRIi64,
|
|
stream->id());
|
|
int rv = nghttp3_conn_shutdown_stream_read(*this, stream->id());
|
|
if (rv == 0) {
|
|
stream->ReceiveStreamReset(final_size, error);
|
|
return;
|
|
}
|
|
|
|
session().SetLastError(
|
|
QuicError::ForApplication(nghttp3_err_infer_quic_app_error_code(rv)));
|
|
session().Close();
|
|
}
|
|
|
|
void StreamStopSending(Stream* stream, QuicError error) override {
|
|
Application::StreamStopSending(stream, error);
|
|
}
|
|
|
|
bool SendHeaders(const Stream& stream,
|
|
HeadersKind kind,
|
|
const v8::Local<v8::Array>& headers,
|
|
HeadersFlags flags = HeadersFlags::NONE) override {
|
|
Session::SendPendingDataScope send_scope(&session());
|
|
Http3Headers nva(env(), headers);
|
|
|
|
switch (kind) {
|
|
case HeadersKind::HINTS: {
|
|
if (!session().is_server()) {
|
|
// Client side cannot send hints
|
|
return false;
|
|
}
|
|
Debug(&session(),
|
|
"Submitting early hints for stream " PRIi64,
|
|
stream.id());
|
|
return nghttp3_conn_submit_info(
|
|
*this, stream.id(), nva.data(), nva.length()) == 0;
|
|
break;
|
|
}
|
|
case HeadersKind::INITIAL: {
|
|
static constexpr nghttp3_data_reader reader = {on_read_data_callback};
|
|
const nghttp3_data_reader* reader_ptr = nullptr;
|
|
|
|
// If the terminal flag is set, that means that we know we're only
|
|
// sending headers and no body and the stream writable side should be
|
|
// closed immediately because there is no nghttp3_data_reader provided.
|
|
if (flags != HeadersFlags::TERMINAL) reader_ptr = &reader;
|
|
|
|
if (session().is_server()) {
|
|
// If this is a server, we're submitting a response...
|
|
Debug(&session(),
|
|
"Submitting response headers for stream " PRIi64,
|
|
stream.id());
|
|
return nghttp3_conn_submit_response(
|
|
*this, stream.id(), nva.data(), nva.length(), reader_ptr);
|
|
} else {
|
|
// Otherwise we're submitting a request...
|
|
Debug(&session(),
|
|
"Submitting request headers for stream " PRIi64,
|
|
stream.id());
|
|
return nghttp3_conn_submit_request(*this,
|
|
stream.id(),
|
|
nva.data(),
|
|
nva.length(),
|
|
reader_ptr,
|
|
const_cast<Stream*>(&stream)) == 0;
|
|
}
|
|
break;
|
|
}
|
|
case HeadersKind::TRAILING: {
|
|
return nghttp3_conn_submit_trailers(
|
|
*this, stream.id(), nva.data(), nva.length()) == 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
StreamPriority GetStreamPriority(const Stream& stream) override {
|
|
nghttp3_pri pri;
|
|
if (nghttp3_conn_get_stream_priority(*this, &pri, stream.id()) == 0) {
|
|
// TODO(@jasnell): Support the incremental flag
|
|
switch (pri.urgency) {
|
|
case NGHTTP3_URGENCY_HIGH:
|
|
return StreamPriority::HIGH;
|
|
case NGHTTP3_URGENCY_LOW:
|
|
return StreamPriority::LOW;
|
|
default:
|
|
return StreamPriority::DEFAULT;
|
|
}
|
|
}
|
|
return StreamPriority::DEFAULT;
|
|
}
|
|
|
|
int GetStreamData(StreamData* data) override {
|
|
ssize_t ret = 0;
|
|
Debug(&session(), "HTTP/3 application getting stream data");
|
|
if (conn_ && session().max_data_left()) {
|
|
nghttp3_vec vec = *data;
|
|
ret = nghttp3_conn_writev_stream(
|
|
*this, &data->id, &data->fin, &vec, data->count);
|
|
if (ret < 0) {
|
|
return static_cast<int>(ret);
|
|
} else {
|
|
data->remaining = data->count = static_cast<size_t>(ret);
|
|
if (data->id > 0) {
|
|
data->stream = session().FindStream(data->id);
|
|
}
|
|
}
|
|
}
|
|
DCHECK_NOT_NULL(data->buf);
|
|
return 0;
|
|
}
|
|
|
|
bool StreamCommit(StreamData* data, size_t datalen) override {
|
|
Debug(&session(),
|
|
"HTTP/3 application committing stream %" PRIi64 " data %zu",
|
|
data->id,
|
|
datalen);
|
|
int err = nghttp3_conn_add_write_offset(*this, data->id, datalen);
|
|
if (err != 0) {
|
|
session().SetLastError(QuicError::ForApplication(
|
|
nghttp3_err_infer_quic_app_error_code(err)));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ShouldSetFin(const StreamData& data) override {
|
|
return data.id > -1 && !is_control_stream(data.id) && data.fin == 1;
|
|
}
|
|
|
|
SET_NO_MEMORY_INFO()
|
|
SET_MEMORY_INFO_NAME(Http3Application)
|
|
SET_SELF_SIZE(Http3Application)
|
|
|
|
private:
|
|
inline operator nghttp3_conn*() const {
|
|
DCHECK_NOT_NULL(conn_.get());
|
|
return conn_.get();
|
|
}
|
|
|
|
bool CreateAndBindControlStreams() {
|
|
Debug(&session(), "Creating and binding HTTP/3 control streams");
|
|
auto stream = session().OpenStream(Direction::UNIDIRECTIONAL);
|
|
if (!stream) return false;
|
|
if (nghttp3_conn_bind_control_stream(*this, stream->id()) != 0) {
|
|
return false;
|
|
}
|
|
|
|
auto enc_stream = session().OpenStream(Direction::UNIDIRECTIONAL);
|
|
if (!enc_stream) return false;
|
|
|
|
auto dec_stream = session().OpenStream(Direction::UNIDIRECTIONAL);
|
|
if (!dec_stream) return false;
|
|
|
|
bool bound = nghttp3_conn_bind_qpack_streams(
|
|
*this, enc_stream->id(), dec_stream->id()) == 0;
|
|
control_stream_id_ = stream->id();
|
|
qpack_enc_stream_id_ = enc_stream->id();
|
|
qpack_dec_stream_id_ = dec_stream->id();
|
|
return bound;
|
|
}
|
|
|
|
inline bool is_control_stream(int64_t id) const {
|
|
return id == control_stream_id_ || id == qpack_dec_stream_id_ ||
|
|
id == qpack_enc_stream_id_;
|
|
}
|
|
|
|
bool is_destroyed() const { return session().is_destroyed(); }
|
|
|
|
Http3ConnectionPointer InitializeConnection() {
|
|
nghttp3_conn* conn = nullptr;
|
|
nghttp3_settings settings = options_;
|
|
if (session().is_server()) {
|
|
CHECK_EQ(nghttp3_conn_server_new(
|
|
&conn, &kCallbacks, &settings, &allocator_, this),
|
|
0);
|
|
} else {
|
|
CHECK_EQ(nghttp3_conn_client_new(
|
|
&conn, &kCallbacks, &settings, &allocator_, this),
|
|
0);
|
|
}
|
|
return Http3ConnectionPointer(conn);
|
|
}
|
|
|
|
void OnStreamClose(Stream* stream, uint64_t app_error_code) {
|
|
if (stream->is_destroyed()) return;
|
|
Debug(&session(),
|
|
"HTTP/3 application received stream close for stream %" PRIi64,
|
|
stream->id());
|
|
auto direction = stream->direction();
|
|
stream->Destroy(QuicError::ForApplication(app_error_code));
|
|
ExtendMaxStreams(EndpointLabel::REMOTE, direction, 1);
|
|
}
|
|
|
|
void OnReceiveData(Stream* stream, const nghttp3_vec& vec) {
|
|
if (stream->is_destroyed()) return;
|
|
Debug(&session(), "HTTP/3 application received %zu bytes of data", vec.len);
|
|
stream->ReceiveData(vec.base, vec.len, Stream::ReceiveDataFlags{});
|
|
}
|
|
|
|
void OnDeferredConsume(Stream* stream, size_t consumed) {
|
|
auto& sess = session();
|
|
Debug(
|
|
&session(), "HTTP/3 application deferred consume %zu bytes", consumed);
|
|
if (!stream->is_destroyed()) {
|
|
sess.ExtendStreamOffset(stream->id(), consumed);
|
|
}
|
|
sess.ExtendOffset(consumed);
|
|
}
|
|
|
|
void OnBeginHeaders(Stream* stream) {
|
|
if (stream->is_destroyed()) return;
|
|
Debug(&session(),
|
|
"HTTP/3 application beginning initial block of headers for stream "
|
|
"%" PRIi64,
|
|
stream->id());
|
|
stream->BeginHeaders(HeadersKind::INITIAL);
|
|
}
|
|
|
|
void OnReceiveHeader(Stream* stream, Http3Header&& header) {
|
|
if (stream->is_destroyed()) return;
|
|
if (header.name() == ":status") {
|
|
if (header.value()[0] == '1') {
|
|
Debug(
|
|
&session(),
|
|
"HTTP/3 application switching to hints headers for stream %" PRIi64,
|
|
stream->id());
|
|
stream->set_headers_kind(HeadersKind::HINTS);
|
|
}
|
|
}
|
|
stream->AddHeader(std::move(header));
|
|
}
|
|
|
|
void OnEndHeaders(Stream* stream, int fin) {
|
|
Debug(&session(),
|
|
"HTTP/3 application received end of headers for stream %" PRIi64,
|
|
stream->id());
|
|
stream->EmitHeaders();
|
|
if (fin != 0) {
|
|
// The stream is done. There's no more data to receive!
|
|
Debug(&session(), "Headers are final for stream %" PRIi64, stream->id());
|
|
OnEndStream(stream);
|
|
}
|
|
}
|
|
|
|
void OnBeginTrailers(Stream* stream) {
|
|
if (stream->is_destroyed()) return;
|
|
Debug(&session(),
|
|
"HTTP/3 application beginning block of trailers for stream %" PRIi64,
|
|
stream->id());
|
|
stream->BeginHeaders(HeadersKind::TRAILING);
|
|
}
|
|
|
|
void OnReceiveTrailer(Stream* stream, Http3Header&& header) {
|
|
stream->AddHeader(header);
|
|
}
|
|
|
|
void OnEndTrailers(Stream* stream, int fin) {
|
|
if (stream->is_destroyed()) return;
|
|
Debug(&session(),
|
|
"HTTP/3 application received end of trailers for stream %" PRIi64,
|
|
stream->id());
|
|
stream->EmitHeaders();
|
|
if (fin != 0) {
|
|
Debug(&session(), "Trailers are final for stream %" PRIi64, stream->id());
|
|
// The stream is done. There's no more data to receive!
|
|
stream->ReceiveData(nullptr,
|
|
0,
|
|
Stream::ReceiveDataFlags{/* .fin = */ true,
|
|
/* .early = */ false});
|
|
}
|
|
}
|
|
|
|
void OnEndStream(Stream* stream) {
|
|
if (stream->is_destroyed()) return;
|
|
Debug(&session(),
|
|
"HTTP/3 application received end of stream for stream %" PRIi64,
|
|
stream->id());
|
|
stream->ReceiveData(nullptr,
|
|
0,
|
|
Stream::ReceiveDataFlags{/* .fin = */ true,
|
|
/* .early = */ false});
|
|
}
|
|
|
|
void OnStopSending(Stream* stream, uint64_t app_error_code) {
|
|
if (stream->is_destroyed()) return;
|
|
Debug(&session(),
|
|
"HTTP/3 application received stop sending for stream %" PRIi64,
|
|
stream->id());
|
|
stream->ReceiveStopSending(QuicError::ForApplication(app_error_code));
|
|
}
|
|
|
|
void OnResetStream(Stream* stream, uint64_t app_error_code) {
|
|
if (stream->is_destroyed()) return;
|
|
Debug(&session(),
|
|
"HTTP/3 application received reset stream for stream %" PRIi64,
|
|
stream->id());
|
|
stream->ReceiveStreamReset(0, QuicError::ForApplication(app_error_code));
|
|
}
|
|
|
|
void OnShutdown() {
|
|
// This callback is invoked when we receive a request to gracefully shutdown
|
|
// the http3 connection. For client, the id is the stream id of a client
|
|
// initiated stream. For server, the id is the stream id of a server
|
|
// initiated stream. Once received, the other side is guaranteed not to
|
|
// process any more data.
|
|
|
|
// On the client side, if id is equal to NGHTTP3_SHUTDOWN_NOTICE_STREAM_ID,
|
|
// or on the server if the id is equal to NGHTTP3_SHUTDOWN_NOTICE_PUSH_ID,
|
|
// then this is a request to begin a graceful shutdown.
|
|
|
|
// This can be called multiple times but the id can only stay the same or
|
|
// *decrease*.
|
|
|
|
// TODO(@jasnell): Need to determine exactly how to handle.
|
|
Debug(&session(), "HTTP/3 application received shutdown notice");
|
|
}
|
|
|
|
void OnReceiveSettings(const nghttp3_settings* settings) {
|
|
options_.enable_connect_protocol = settings->enable_connect_protocol;
|
|
options_.enable_datagrams = settings->h3_datagram;
|
|
options_.max_field_section_size = settings->max_field_section_size;
|
|
options_.qpack_blocked_streams = settings->qpack_blocked_streams;
|
|
options_.qpack_encoder_max_dtable_capacity =
|
|
settings->qpack_encoder_max_dtable_capacity;
|
|
options_.qpack_max_dtable_capacity = settings->qpack_max_dtable_capacity;
|
|
Debug(
|
|
&session(), "HTTP/3 application received updated settings ", options_);
|
|
}
|
|
|
|
bool started_ = false;
|
|
nghttp3_mem allocator_;
|
|
Session::Application_Options options_;
|
|
Http3ConnectionPointer conn_;
|
|
int64_t control_stream_id_ = -1;
|
|
int64_t qpack_dec_stream_id_ = -1;
|
|
int64_t qpack_enc_stream_id_ = -1;
|
|
|
|
// ==========================================================================
|
|
// Static callbacks
|
|
|
|
static Http3Application* From(nghttp3_conn* conn, void* user_data) {
|
|
DCHECK_NOT_NULL(user_data);
|
|
auto app = static_cast<Http3Application*>(user_data);
|
|
DCHECK_EQ(conn, app->conn_.get());
|
|
return app;
|
|
}
|
|
|
|
static Stream* From(int64_t stream_id, void* stream_user_data) {
|
|
DCHECK_NOT_NULL(stream_user_data);
|
|
auto stream = static_cast<Stream*>(stream_user_data);
|
|
DCHECK_EQ(stream_id, stream->id());
|
|
return stream;
|
|
}
|
|
|
|
#define NGHTTP3_CALLBACK_SCOPE(name) \
|
|
auto name = From(conn, conn_user_data); \
|
|
if (name->is_destroyed()) [[unlikely]] { \
|
|
return NGHTTP3_ERR_CALLBACK_FAILURE; \
|
|
} \
|
|
NgHttp3CallbackScope scope(name->env());
|
|
|
|
static nghttp3_ssize on_read_data_callback(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
nghttp3_vec* vec,
|
|
size_t veccnt,
|
|
uint32_t* pflags,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
return 0;
|
|
}
|
|
|
|
static int on_acked_stream_data(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
uint64_t datalen,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->AcknowledgeStreamData(stream, static_cast<size_t>(datalen));
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_stream_close(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
uint64_t app_error_code,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnStreamClose(stream, app_error_code);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_receive_data(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
const uint8_t* data,
|
|
size_t datalen,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnReceiveData(stream,
|
|
nghttp3_vec{const_cast<uint8_t*>(data), datalen});
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_deferred_consume(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
size_t consumed,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnDeferredConsume(stream, consumed);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_begin_headers(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnBeginHeaders(stream);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_receive_header(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
int32_t token,
|
|
nghttp3_rcbuf* name,
|
|
nghttp3_rcbuf* value,
|
|
uint8_t flags,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
if (Http3Header::IsZeroLength(token, name, value)) return NGTCP2_SUCCESS;
|
|
app->OnReceiveHeader(stream,
|
|
Http3Header(app->env(), token, name, value, flags));
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_end_headers(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
int fin,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnEndHeaders(stream, fin);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_begin_trailers(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnBeginTrailers(stream);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_receive_trailer(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
int32_t token,
|
|
nghttp3_rcbuf* name,
|
|
nghttp3_rcbuf* value,
|
|
uint8_t flags,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
if (Http3Header::IsZeroLength(token, name, value)) return NGTCP2_SUCCESS;
|
|
app->OnReceiveTrailer(stream,
|
|
Http3Header(app->env(), token, name, value, flags));
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_end_trailers(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
int fin,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnEndTrailers(stream, fin);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_end_stream(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnEndStream(stream);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_stop_sending(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
uint64_t app_error_code,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnStopSending(stream, app_error_code);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_reset_stream(nghttp3_conn* conn,
|
|
int64_t stream_id,
|
|
uint64_t app_error_code,
|
|
void* conn_user_data,
|
|
void* stream_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
auto stream = From(stream_id, stream_user_data);
|
|
if (stream == nullptr) return NGHTTP3_ERR_CALLBACK_FAILURE;
|
|
app->OnResetStream(stream, app_error_code);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_shutdown(nghttp3_conn* conn, int64_t id, void* conn_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
app->OnShutdown();
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static int on_receive_settings(nghttp3_conn* conn,
|
|
const nghttp3_settings* settings,
|
|
void* conn_user_data) {
|
|
NGHTTP3_CALLBACK_SCOPE(app);
|
|
app->OnReceiveSettings(settings);
|
|
return NGTCP2_SUCCESS;
|
|
}
|
|
|
|
static constexpr nghttp3_callbacks kCallbacks = {on_acked_stream_data,
|
|
on_stream_close,
|
|
on_receive_data,
|
|
on_deferred_consume,
|
|
on_begin_headers,
|
|
on_receive_header,
|
|
on_end_headers,
|
|
on_begin_trailers,
|
|
on_receive_trailer,
|
|
on_end_trailers,
|
|
on_stop_sending,
|
|
on_end_stream,
|
|
on_reset_stream,
|
|
on_shutdown,
|
|
on_receive_settings};
|
|
};
|
|
} // namespace
|
|
|
|
std::unique_ptr<Session::Application> createHttp3Application(
|
|
Session* session, const Session::Application_Options& options) {
|
|
return std::make_unique<Http3Application>(session, options);
|
|
}
|
|
|
|
} // namespace node::quic
|
|
|
|
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|