#pragma once
#include <aliased_struct.h>
#include <env.h>
#include <nghttp3/nghttp3.h>
#include <ngtcp2/ngtcp2.h>
#include <node_errors.h>
#include <uv.h>
#include <v8.h>
#include <limits>
namespace node::quic {
#define NGTCP2_SUCCESS 0
#define NGTCP2_ERR(V) ((V) != NGTCP2_SUCCESS)
#define NGTCP2_OK(V) ((V) == NGTCP2_SUCCESS)
#define IF_QUIC_DEBUG(env) \
if (env->enabled_debug_list()->enabled(DebugCategory::QUIC)) [[unlikely]]
#define DISALLOW_COPY(Name) \
Name(const Name&) = delete; \
Name& operator=(const Name&) = delete;
#define DISALLOW_MOVE(Name) \
Name(Name&&) = delete; \
Name& operator=(Name&&) = delete;
#define DISALLOW_COPY_AND_MOVE(Name) \
DISALLOW_COPY(Name) \
DISALLOW_MOVE(Name)
template <typename Opt, std::string Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
if (!value->IsUndefined()) {
Utf8Value utf8(env->isolate(), value);
options->*member = utf8.ToString();
}
return true;
}
template <typename Opt, bool Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
if (!value->IsUndefined()) {
options->*member = value->BooleanValue(env->isolate());
}
return true;
}
template <typename Opt, uint32_t Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
if (!value->IsUndefined()) {
if (!value->IsUint32()) {
Utf8Value nameStr(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(
env, "The %s option must be an uint32", nameStr);
return false;
}
v8::Local<v8::Uint32> num;
if (!value->ToUint32(env->context()).ToLocal(&num)) {
Utf8Value nameStr(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(
env, "The %s option must be an uint32", nameStr);
return false;
}
options->*member = num->Value();
}
return true;
}
template <typename Opt, uint16_t Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
if (!value->IsUndefined()) {
if (!value->IsUint32()) {
Utf8Value nameStr(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(
env, "The %s option must be an uint16", nameStr);
return false;
}
v8::Local<v8::Uint32> num;
if (!value->ToUint32(env->context()).ToLocal(&num)) {
Utf8Value nameStr(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(
env, "The %s option must be an uint16", nameStr);
return false;
}
uint32_t val = num->Value();
if (val > 0xFFFF) {
Utf8Value nameStr(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(
env, "The %s option must fit in a uint16", nameStr);
return false;
}
options->*member = static_cast<uint16_t>(val);
}
return true;
}
template <typename Opt, uint64_t Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
if (!value->IsUndefined()) {
if (!value->IsBigInt() && !value->IsNumber()) {
Utf8Value nameStr(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(
env, "option %s must be a bigint or number", nameStr);
return false;
}
DCHECK_IMPLIES(!value->IsBigInt(), value->IsNumber());
uint64_t val = 0;
if (value->IsBigInt()) {
bool lossless = true;
val = value.As<v8::BigInt>()->Uint64Value(&lossless);
if (!lossless) {
Utf8Value label(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(env, "option %s is out of range", label);
return false;
}
} else {
double dbl = value.As<v8::Number>()->Value();
if (dbl < 0) {
Utf8Value label(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(env, "option %s is out of range", label);
return false;
}
val = static_cast<uint64_t>(dbl);
}
options->*member = val;
}
return true;
}
// Utilities used to update the stats for Endpoint, Session, and Stream
// objects. The stats themselves are maintained in an AliasedStruct within
// each of the relevant classes.
template <typename Stats, uint64_t Stats::*member>
void IncrementStat(Stats* stats, uint64_t amt = 1) {
stats->*member += amt;
}
template <typename Stats, uint64_t Stats::*member>
void RecordTimestampStat(Stats* stats) {
stats->*member = uv_hrtime();
}
template <typename Stats, uint64_t Stats::*member>
void SetStat(Stats* stats, uint64_t val) {
stats->*member = val;
}
template <typename Stats, uint64_t Stats::*member>
uint64_t GetStat(Stats* stats) {
return stats->*member;
}
#define STAT_INCREMENT(Type, name) \
IncrementStat<Type, &Type::name>(stats_.Data());
#define STAT_INCREMENT_N(Type, name, amt) \
IncrementStat<Type, &Type::name>(stats_.Data(), amt);
#define STAT_RECORD_TIMESTAMP(Type, name) \
RecordTimestampStat<Type, &Type::name>(stats_.Data());
#define STAT_SET(Type, name, val) SetStat<Type, &Type::name>(stats_.Data(), val)
#define STAT_GET(Type, name) GetStat<Type, &Type::name>(stats_.Data())
#define STAT_FIELD(_, name) uint64_t name;
#define STAT_STRUCT(klass, name) \
struct klass::Stats final { \
name##_STATS(STAT_FIELD) \
};
#define JS_METHOD_IMPL(name) \
void name(const v8::FunctionCallbackInfo<v8::Value>& args)
#define JS_METHOD(name) static JS_METHOD_IMPL(name)
#define JS_CONSTRUCTOR(name) \
inline static bool HasInstance(Environment* env, \
v8::Local<v8::Value> value) { \
return GetConstructorTemplate(env)->HasInstance(value); \
} \
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate( \
Environment* env)
#define JS_CONSTRUCTOR_IMPL(name, template, body) \
v8::Local<v8::FunctionTemplate> name::GetConstructorTemplate( \
Environment* env) { \
auto& state = BindingData::Get(env); \
auto tmpl = state.template(); \
if (tmpl.IsEmpty()) { \
body state.set_##template(tmpl); \
} \
return tmpl; \
}
#define JS_ILLEGAL_CONSTRUCTOR() \
tmpl = NewFunctionTemplate(env->isolate(), IllegalConstructor)
#define JS_NEW_CONSTRUCTOR() tmpl = NewFunctionTemplate(env->isolate(), New)
#define JS_INHERIT(name) tmpl->Inherit(name::GetConstructorTemplate(env));
#define JS_CLASS(name) \
tmpl->InstanceTemplate()->SetInternalFieldCount(kInternalFieldCount); \
tmpl->SetClassName(state.name##_string())
#define JS_CLASS_FIELDS(name, fields) \
tmpl->InstanceTemplate()->SetInternalFieldCount(fields); \
tmpl->SetClassName(state.name##_string())
#define JS_NEW_INSTANCE_OR_RETURN(env, name, ret) \
v8::Local<v8::Object> name; \
if (!GetConstructorTemplate(env) \
->InstanceTemplate() \
->NewInstance(env->context()) \
.ToLocal(&name)) { \
return ret; \
}
#define JS_NEW_INSTANCE(env, name) \
v8::Local<v8::Object> name; \
if (!GetConstructorTemplate(env) \
->InstanceTemplate() \
->NewInstance(env->context()) \
.ToLocal(&name)) { \
return; \
}
#define JS_BINDING_INIT_BOILERPLATE() \
static void InitPerIsolate(IsolateData* isolate_data, \
v8::Local<v8::ObjectTemplate> target); \
static void InitPerContext(Realm* realm, v8::Local<v8::Object> target); \
static void RegisterExternalReferences(ExternalReferenceRegistry* registry)
#define JS_TRY_ALLOCATE_BACKING(env, name, len) \
auto name = v8::ArrayBuffer::NewBackingStore( \
env->isolate(), \
len, \
v8::BackingStoreInitializationMode::kUninitialized, \
v8::BackingStoreOnFailureMode::kReturnNull); \
if (!name) { \
THROW_ERR_MEMORY_ALLOCATION_FAILED(env); \
return; \
}
#define JS_TRY_ALLOCATE_BACKING_OR_RETURN(env, name, len, ret) \
auto name = v8::ArrayBuffer::NewBackingStore( \
env->isolate(), \
len, \
v8::BackingStoreInitializationMode::kUninitialized, \
v8::BackingStoreOnFailureMode::kReturnNull); \
if (!name) { \
THROW_ERR_MEMORY_ALLOCATION_FAILED(env); \
return ret; \
}
#define JS_DEFINE_READONLY_PROPERTY(env, target, name, value) \
target \
->DefineOwnProperty( \
env->context(), name, value, v8::PropertyAttribute::ReadOnly) \
.Check();
enum class Side : uint8_t {
CLIENT,
SERVER,
};
enum class Direction : uint8_t {
BIDIRECTIONAL,
UNIDIRECTIONAL,
};
enum class StreamPriority : uint8_t {
DEFAULT = NGHTTP3_DEFAULT_URGENCY,
LOW = NGHTTP3_URGENCY_LOW,
HIGH = NGHTTP3_URGENCY_HIGH,
};
enum class StreamPriorityFlags : uint8_t {
NON_INCREMENTAL,
INCREMENTAL,
};
enum class HeadersSupportState : uint8_t {
UNKNOWN,
SUPPORTED,
UNSUPPORTED,
};
enum class StreamCallbacksSupportState : uint8_t {
UNKNOWN,
SUPPORTED,
UNSUPPORTED,
};
enum class PathValidationResult : uint8_t {
SUCCESS = NGTCP2_PATH_VALIDATION_RESULT_SUCCESS,
FAILURE = NGTCP2_PATH_VALIDATION_RESULT_FAILURE,
ABORTED = NGTCP2_PATH_VALIDATION_RESULT_ABORTED,
};
enum class DatagramStatus : uint8_t {
ACKNOWLEDGED,
LOST,
ABANDONED,
};
#define CC_ALGOS(V) \
V(RENO, reno) \
V(CUBIC, cubic) \
V(BBR, bbr)
#define V(name, _) static constexpr auto CC_ALGO_##name = NGTCP2_CC_ALGO_##name;
CC_ALGOS(V)
#undef V
using error_code = uint64_t;
using stream_id = int64_t;
using datagram_id = uint64_t;
constexpr size_t kDefaultMaxPacketLength = NGTCP2_MAX_UDP_PAYLOAD_SIZE;
constexpr uint64_t kMaxSizeT = std::numeric_limits<size_t>::max();
constexpr uint64_t kMaxSafeJsInteger = 9007199254740991;
constexpr auto kSocketAddressInfoTimeout = 60 * NGTCP2_SECONDS;
constexpr size_t kMaxVectorCount = 16;
constexpr stream_id kMaxStreamId = std::numeric_limits<stream_id>::max();
// A token bucket rate limiter using lazy refill. No timer needed — tokens
// are computed on demand from the elapsed time since the last check.
// Used to cap the total rate of stateless responses (retry, reset,
// version negotiation, immediate close) regardless of source address,
// preventing spoofed-source floods from bypassing per-host limits.
struct TokenBucket final {
double rate; // tokens per second (refill rate)
double burst; // maximum tokens (bucket capacity)
double tokens; // current token count
uint64_t last_ts; // last refill timestamp (nanoseconds, uv_hrtime)
TokenBucket() : rate(0), burst(0), tokens(0), last_ts(0) {}
TokenBucket(double rate, double burst);
// Reinitialize the bucket with new rate/burst parameters if it
// hasn't been initialized yet (last_ts == 0). Used for per-host
// buckets in the address LRU where the rate/burst aren't known
// at construction time.
void InitOnce(double r, double b, uint64_t now);
// Try to consume one token. Refills based on elapsed time, then
// attempts to consume. Returns true if the request is allowed.
// The caller provides the current timestamp to avoid redundant
// uv_hrtime() calls in hot paths.
bool consume(uint64_t now);
};
class DebugIndentScope final {
public:
inline DebugIndentScope() { ++indent_; }
DISALLOW_COPY_AND_MOVE(DebugIndentScope)
inline ~DebugIndentScope() { --indent_; }
inline std::string Prefix() const {
std::string res("\n");
res.append(indent_, '\t');
return res;
}
inline std::string Close() const {
std::string res("\n");
res.append(indent_ - 1, '\t');
res += "}";
return res;
}
private:
// NOLINTNEXTLINE(runtime/thread_local)
static thread_local int indent_;
};
} // namespace node::quic