%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/quic/
Upload File :
Create Path :
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/quic/http3.cc

#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 {
namespace 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);
      }
    }
    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 (UNLIKELY(name->is_destroyed())) 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 quic
}  // namespace node

#endif  // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

Zerion Mini Shell 1.0