Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
quic: improve packet definition
Handful of additional improvements to the Packet class.

Signed-off-by: James M Snell <[email protected]>
Assisted-by: Opencode:Opus 4.6
  • Loading branch information
jasnell committed Apr 4, 2026
commit 1bf0dc4cb0080f247940408172b4fc77a7056009
6 changes: 3 additions & 3 deletions src/quic/arena.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ class ArenaPool final : public MemoryRetainer {
// Construct T in an acquired slot with forwarded args.
// Returns an empty Ptr only on allocation failure.
template <typename... Args>
Ptr Acquire(Args&&... args);
[[nodiscard]] Ptr Acquire(Args&&... args);

// Construct T with (extra_data_ptr, extra_bytes, ...args).
// Use this for types whose constructor accepts a trailing data
// buffer as its first two parameters.
template <typename... Args>
Ptr AcquireExtra(Args&&... args);
[[nodiscard]] Ptr AcquireExtra(Args&&... args);

// Release a raw T* previously detached via Ptr::release().
// Calls ~T() and returns the slot to the pool's free list.
Expand Down Expand Up @@ -199,7 +199,7 @@ class ArenaPool<T>::Ptr final {
// Detach ownership. The caller takes responsibility for eventually
// calling ArenaPool<T>::Release(ptr) to destruct T and return
// the slot to the pool.
T* release() noexcept {
[[nodiscard]] T* release() noexcept {
if (!slot_) return nullptr;
T* obj = ObjectFromSlot(slot_);
slot_ = nullptr;
Expand Down
59 changes: 6 additions & 53 deletions src/quic/packet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
#include "packet.h"
#include "tokens.h"

namespace node {
namespace quic {
namespace node::quic {

namespace {
static constexpr size_t kRandlen = NGTCP2_MIN_STATELESS_RESET_RANDLEN * 5;
Expand All @@ -43,57 +42,12 @@ Packet::Packet(uint8_t* data,
size_t capacity,
Listener* listener,
const SocketAddress& destination)
: req_{},
: data_(data),
capacity_(capacity),
length_(capacity),
listener_(listener),
destination_(destination),
data_(data),
capacity_(capacity),
length_(capacity) {}

const SocketAddress& Packet::destination() const {
return destination_;
}

Packet::Listener* Packet::listener() const {
return listener_;
}

size_t Packet::length() const {
return length_;
}

size_t Packet::capacity() const {
return capacity_;
}

uint8_t* Packet::data() {
return data_;
}

const uint8_t* Packet::data() const {
return data_;
}

Packet::operator uv_buf_t() const {
return uv_buf_init(reinterpret_cast<char*>(data_), length_);
}

Packet::operator ngtcp2_vec() const {
return ngtcp2_vec{data_, length_};
}

void Packet::Truncate(size_t len) {
DCHECK_LE(len, capacity_);
length_ = len;
}

uv_udp_send_t* Packet::req() {
return &req_;
}

Packet* Packet::FromReq(uv_udp_send_t* req) {
return ContainerOf(&Packet::req_, req);
}
req_{} {}

std::string Packet::ToString() const {
std::string res = "Packet(";
Expand Down Expand Up @@ -264,8 +218,7 @@ Packet::Ptr Packet::CreateVersionNegotiationPacket(
return packet;
}

} // namespace quic
} // namespace node
} // namespace node::quic

#endif // OPENSSL_NO_QUIC
#endif // HAVE_OPENSSL && HAVE_QUIC
78 changes: 49 additions & 29 deletions src/quic/packet.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include <ngtcp2/ngtcp2.h>
#include <node_sockaddr.h>
#include <uv.h>
#include <v8.h>
#include <string>
#include "arena.h"
#include "cid.h"
Expand Down Expand Up @@ -39,6 +38,10 @@ struct PathDescriptor final {
//
// Packets are always encrypted; their content is opaque. We leave it
// entirely up to ngtcp2 how to encode QUIC frames into the packet.
//
// Member layout is ordered so that fields touched on the hot path
// (data_, capacity_, length_, listener_) share the first cache line.
// The uv_udp_send_t (320 bytes, only touched by libuv) is placed last.
class Packet final {
public:
using Ptr = ArenaPool<Packet>::Ptr;
Expand All @@ -58,49 +61,61 @@ class Packet final {

DISALLOW_COPY_AND_MOVE(Packet)

const SocketAddress& destination() const;
Listener* listener() const;
size_t length() const;
size_t capacity() const;
uint8_t* data();
const uint8_t* data() const;
operator uv_buf_t() const;
operator ngtcp2_vec() const;
// --- Inline accessors (hot path) ---

uint8_t* data() { return data_; }
const uint8_t* data() const { return data_; }
size_t length() const { return length_; }
size_t capacity() const { return capacity_; }
const SocketAddress& destination() const { return destination_; }
Listener* listener() const { return listener_; }
uv_udp_send_t* req() { return &req_; }

operator uv_buf_t() const {
return uv_buf_init(reinterpret_cast<char*>(data_), length_);
}
operator ngtcp2_vec() const { return ngtcp2_vec{data_, length_}; }

// Modify the logical size of the packet after ngtcp2 has written
// to it. len must be <= capacity().
void Truncate(size_t len);

uv_udp_send_t* req();
void Truncate(size_t len) {
DCHECK_LE(len, capacity_);
length_ = len;
}

// Recover Packet* from a uv_udp_send_t* in the libuv send callback.
static Packet* FromReq(uv_udp_send_t* req);
static Packet* FromReq(uv_udp_send_t* req) {
return ContainerOf(&Packet::req_, req);
}

// --- Static factory methods ---
// These create fully-formed packets for specific QUIC operations.
// They acquire from the endpoint's packet pool and return Ptr.
// An empty Ptr indicates failure.

static Ptr CreateRetryPacket(Endpoint& endpoint,
const PathDescriptor& path_descriptor,
const TokenSecret& token_secret);
[[nodiscard]] static Ptr CreateRetryPacket(
Endpoint& endpoint,
const PathDescriptor& path_descriptor,
const TokenSecret& token_secret);

static Ptr CreateConnectionClosePacket(Endpoint& endpoint,
const SocketAddress& destination,
ngtcp2_conn* conn,
const QuicError& error);
[[nodiscard]] static Ptr CreateConnectionClosePacket(
Endpoint& endpoint,
const SocketAddress& destination,
ngtcp2_conn* conn,
const QuicError& error);

static Ptr CreateImmediateConnectionClosePacket(
[[nodiscard]] static Ptr CreateImmediateConnectionClosePacket(
Endpoint& endpoint,
const PathDescriptor& path_descriptor,
const QuicError& reason);

static Ptr CreateStatelessResetPacket(Endpoint& endpoint,
const PathDescriptor& path_descriptor,
const TokenSecret& token_secret,
size_t source_len);
[[nodiscard]] static Ptr CreateStatelessResetPacket(
Endpoint& endpoint,
const PathDescriptor& path_descriptor,
const TokenSecret& token_secret,
size_t source_len);

static Ptr CreateVersionNegotiationPacket(
[[nodiscard]] static Ptr CreateVersionNegotiationPacket(
Endpoint& endpoint, const PathDescriptor& path_descriptor);

// --- Diagnostic label: zero cost in release builds ---
Expand All @@ -113,12 +128,17 @@ class Packet final {
std::string ToString() const;

private:
uv_udp_send_t req_;
Listener* listener_;
SocketAddress destination_;
// Hot fields first — all on cache line 0 during the fill loop.
uint8_t* data_;
size_t capacity_;
size_t length_;
Listener* listener_;

// Touched at send time.
SocketAddress destination_;

// Only touched by libuv during uv_udp_send and in the send callback.
uv_udp_send_t req_;

#ifdef DEBUG
const char* diagnostic_label_ = nullptr;
Expand Down