#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "bindingdata.h"
#include "defs.h"
#include "session.h"
#include "sessionticket.h"
#include "streams.h"
namespace node::quic {
enum class HeadersKind : uint8_t {
HINTS,
INITIAL,
TRAILING,
};
enum class HeadersFlags : uint8_t {
NONE,
TERMINAL,
};
// An Application implements the ALPN-protocol specific semantics on behalf
// of a QUIC Session.
class Session::Application : public MemoryRetainer {
public:
using Options = Session::Application_Options;
Application(Session* session, const Options& options);
DISALLOW_COPY_AND_MOVE(Application)
// Get the active options for this application. These may differ from the
// options passed at construction time since some options can be negotiated.
virtual const Options& options() const = 0;
// The type of Application, exposed via the session state so JS
// can observe which Application was selected after ALPN negotiation.
// This is used primarily for testing/debugging.
enum class Type : uint8_t {
NONE = 0, // Not yet selected (server pre-negotiation)
DEFAULT = 1, // DefaultApplication (non-h3 ALPN)
HTTP3 = 2, // Http3ApplicationImpl (h3 / h3-XX ALPN)
};
virtual Type type() const = 0;
virtual bool Start();
// Returns true if Start() has been called successfully.
virtual bool is_started() const { return false; }
// Called when the server rejects 0-RTT early data. The application
// must destroy all streams that were opened during the 0-RTT phase
// since ngtcp2 has already discarded their internal state.
virtual void EarlyDataRejected() = 0;
// The "no error code" is the application-level error code that signals
// "no error". Per the QUIC spec, this can vary by application protocol
// and is not necessarily 0.
virtual error_code GetNoErrorCode() const = 0;
// The "internal error code" is the application-level error code used
// to signal a non-specific failure when no more specific code has
// been provided by the caller. For example, `writer.fail(reason)` on
// the JS side uses this code when `reason` is not a `QuicError`
// carrying an explicit code. For HTTP/3 this is
// `NGHTTP3_H3_INTERNAL_ERROR` (0x102); for raw QUIC applications
// there is no defined application code so we fall back to
// NGTCP2_INTERNAL_ERROR (0x1).
virtual error_code GetInternalErrorCode() const = 0;
// The "request rejected" code is sent on RESET_STREAM when an incoming
// request stream is rejected without any application processing (e.g.
// the session has no consumer for it), so the peer learns the request
// was not processed. For HTTP/3 this is NGHTTP3_H3_REQUEST_REJECTED
// (0x10b); other applications have no such semantic and reuse the
// "no error" code.
virtual error_code GetRequestRejectedCode() const = 0;
// Called after Session::Receive processes a packet, outside all callback
// scopes. Applications can use this to handle deferred operations that
// require calling into JS (e.g., HTTP/3 GOAWAY processing).
virtual void PostReceive() {}
// Called when ngtcp2 notifies us that a new remote stream has been
// opened. The Application decides whether to create a Stream object
// (and fire the JS onstream callback) based on the stream type. For
// example, HTTP/3 only creates Stream objects for bidi streams since
// uni streams are managed internally by nghttp3.
virtual bool ReceiveStreamOpen(stream_id id) = 0;
// Session will forward all received stream data immediately on to the
// Application without any additional processing. Every byte delivered here
// is charged against both the session-level and the stream-level receive
// window, and it is up to the Application to return that credit (see
// ReturnConnectionCredit and Stream::ReturnFlowControlCredit) once the
// bytes have been consumed or discarded.
virtual bool ReceiveStreamData(stream_id id,
const uint8_t* data,
size_t datalen,
const Stream::ReceiveDataFlags& flags,
void* stream_user_data) = 0;
// Session will forward all data acknowledgements for a stream to the
// Application.
virtual bool AcknowledgeStreamData(stream_id id, size_t datalen);
// Called when a pending transport stream receives its stream ID. Protocols
// can use this to flush operations that require an opened stream. Returns
// false if deferred application data could not be submitted.
virtual bool StreamOpened(Stream& stream) { return true; }
// Called when ngtcp2 reports NGTCP2_ERR_STREAM_SHUT_WR for a stream.
// Applications that manage their own framing (e.g., HTTP/3) must inform
// their protocol layer that the stream's write side is shut so it stops
// queuing data for that stream. The default is a no-op.
virtual void StreamWriteShut(stream_id id) {}
// Called to mark the identified stream as being blocked. Not all
// Application types will support blocked streams, and those that do will do
// so differently.
virtual void BlockStream(stream_id id) {}
// Called when the session determines that there is outbound data available
// to send for the given stream.
virtual void ResumeStream(stream_id id) {}
// Returns true if the application manages stream FIN internally (e.g.,
// HTTP/3 uses nghttp3 which sends FIN via the fin flag in writev_stream).
// When true, the stream infrastructure must NOT call
// ngtcp2_conn_shutdown_stream_write when the JS write side ends —
// the application protocol layer handles it.
virtual bool stream_fin_managed_by_application() const { return false; }
// Called when the Session determines that the flow control window for the
// given stream has been expanded. Not all Application types will require
// this notification so the default is to do nothing.
virtual void ExtendMaxStreamData(Stream* stream, uint64_t max_data) {
Debug(session_, "Application extending max stream data");
// By default do nothing.
}
// Different Applications may wish to set some application data in the
// session ticket (e.g. http/3 would set server settings in the application
// data). The first byte written MUST be the Application::Type enum value.
// By default, writes just the type byte.
virtual void CollectSessionTicketAppData(
SessionTicket::AppData* app_data) const;
// Validates the application data embedded in a session ticket offered by
// a resuming client, and decides whether the ticket may be used. The
// Application is always installed by the time a ticket can be decrypted,
// so each Application checks its own data here. By default, there's
// nothing to get.
virtual SessionTicket::AppData::Status ExtractSessionTicketAppData(
const SessionTicket::AppData& app_data,
SessionTicket::AppData::Source::Flag flag);
// Notifies the Application that the identified stream has been closed.
virtual void ReceiveStreamClose(stream_id id,
Stream* stream,
QuicError&& error = QuicError());
// Notifies the Application that the Stream for the identified stream has
// been removed from the session and may be freed immediately afterwards.
// Applications caching the Stream pointer must drop it here.
virtual void StreamRemoved(stream_id id) {}
// Notifies the Application that the identified stream has been reset.
virtual void ReceiveStreamReset(Stream* stream,
uint64_t final_size,
QuicError&& error = QuicError());
// Notifies the Application that the identified stream should stop sending.
virtual void ReceiveStreamStopSending(Stream* stream,
QuicError&& error = QuicError());
// Submits an outbound block of headers for the given stream. Not all
// Application types will support headers, in which case this function
// should return false.
virtual bool SendHeaders(Stream& stream,
HeadersKind kind,
const v8::Local<v8::Array>& headers,
HeadersFlags flags = HeadersFlags::NONE) {
return false;
}
// Updates JavaScript callback interest for an application's stream header
// events. Applications without header semantics ignore this.
virtual void SetHeadersInterest(Stream& stream,
bool wants_headers,
bool wants_trailers) {}
// Returns true if the application protocol supports sending and
// receiving headers on streams (e.g. HTTP/3). Applications that
// do not support headers should return false (the default).
virtual bool SupportsHeaders() const { return false; }
// True if this application dispatches the session-level stream
// callbacks (onheaders et al) for incoming streams when they are
// registered on the session.
virtual bool SupportsStreamCallbacks() const { return false; }
// Initiates application-level graceful shutdown signaling (e.g.,
// HTTP/3 GOAWAY). Called when Session::Close(GRACEFUL) is invoked.
virtual void BeginShutdown() {}
// Completes the application-level graceful shutdown. Called from
// FinishClose() before CONNECTION_CLOSE is sent. For HTTP/3, this
// sends the final GOAWAY with the actual last accepted stream ID.
virtual void CompleteShutdown() {}
// Set the priority level of the stream if supported by the application. Not
// all applications support priorities, in which case this function is a
// non-op.
virtual void SetStreamPriority(
const Stream& stream,
StreamPriority priority = StreamPriority::DEFAULT,
StreamPriorityFlags flags = StreamPriorityFlags::NON_INCREMENTAL) {}
struct StreamPriorityResult {
StreamPriority priority;
StreamPriorityFlags flags;
};
// Get the priority level of the stream if supported by the application. Not
// all applications support priorities, in which case this function returns
// the default stream priority.
virtual StreamPriorityResult GetStreamPriority(const Stream& stream) {
return {StreamPriority::DEFAULT, StreamPriorityFlags::NON_INCREMENTAL};
}
virtual int GetStreamData(StreamData* data) = 0;
virtual bool StreamCommit(StreamData* data, size_t datalen) = 0;
inline Environment* env() const { return session().env(); }
inline Session& session() {
CHECK_NOT_NULL(session_);
return *session_;
}
inline const Session& session() const {
CHECK_NOT_NULL(session_);
return *session_;
}
// Returns the connection-level flow control credit for `datalen` bytes that
// were delivered to the Application but discarded without ever reaching a
// Stream. Dropping them silently would permanently shrink the session's
// shared receive window.
void ReturnConnectionCredit(size_t datalen);
private:
Session* session_ = nullptr;
};
// Create a DefaultApplication for the given session.
std::unique_ptr<Session::Application> CreateDefaultApplication(
Session* session, const Session::Application_Options& options);
} // namespace node::quic
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS