Coverage Report

Created: 2026-01-21 08:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/node/src/quic/session.h
Line
Count
Source
1
#pragma once
2
3
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
4
5
#include <async_wrap.h>
6
#include <base_object.h>
7
#include <env.h>
8
#include <memory_tracker.h>
9
#include <ngtcp2/ngtcp2.h>
10
#include <node_http_common.h>
11
#include <node_sockaddr.h>
12
#include <timer_wrap.h>
13
#include <util.h>
14
#include <optional>
15
#include "bindingdata.h"
16
#include "cid.h"
17
#include "data.h"
18
#include "defs.h"
19
#include "logstream.h"
20
#include "packet.h"
21
#include "preferredaddress.h"
22
#include "sessionticket.h"
23
#include "streams.h"
24
#include "tlscontext.h"
25
#include "transportparams.h"
26
27
namespace node::quic {
28
29
class Endpoint;
30
31
// A Session represents one half of a persistent connection between two QUIC
32
// peers. Every Session is established first by performing a TLS handshake in
33
// which the client sends an initial packet to the server containing a TLS
34
// client hello. Once the TLS handshake has been completed, the Session can be
35
// used to open one or more Streams for the actual data flow back and forth.
36
//
37
// While client and server Sessions are created in slightly different ways,
38
// their lifecycles are generally identical:
39
//
40
// A Session is either acting as a Client or as a Server.
41
//
42
// Client Sessions are always created using Endpoint::Connect()
43
//
44
// Server Sessions are always created by an Endpoint receiving a valid initial
45
// request received from a remote client.
46
//
47
// As soon as Sessions of either type are created, they will immediately start
48
// working through the TLS handshake to establish the crypographic keys used to
49
// secure the communication. Once those keys are established, the Session can be
50
// used to open Streams. Based on how the Session is configured, any number of
51
// Streams can exist concurrently on a single Session.
52
//
53
// The Session wraps an ngtcp2_conn that is initialized when the session object
54
// is created. This ngtcp2_conn is destroyed when the session object is freed.
55
// However, the session can be in a closed/destroyed state and still have a
56
// valid ngtcp2_conn pointer. This is important because the ngtcp2 still might
57
// be processing data within the scope of an ngtcp2_conn after the session
58
// object itself is closed/destroyed by user code.
59
class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
60
 public:
61
  // For simplicity, we use the same Application::Options struct for all
62
  // Application types. This may change in the future. Not all of the options
63
  // are going to be relevant for all Application types.
64
  struct Application_Options final : public MemoryRetainer {
65
    // The maximum number of header pairs permitted for a Stream.
66
    // Only relevant if the selected application supports headers.
67
    uint64_t max_header_pairs = DEFAULT_MAX_HEADER_LIST_PAIRS;
68
69
    // The maximum total number of header bytes (including header
70
    // name and value) permitted for a Stream.
71
    // Only relevant if the selected application supports headers.
72
    uint64_t max_header_length = DEFAULT_MAX_HEADER_LENGTH;
73
74
    // HTTP/3 specific options.
75
    uint64_t max_field_section_size = 0;
76
    uint64_t qpack_max_dtable_capacity = 0;
77
    uint64_t qpack_encoder_max_dtable_capacity = 0;
78
    uint64_t qpack_blocked_streams = 0;
79
80
    bool enable_connect_protocol = true;
81
    bool enable_datagrams = true;
82
83
    operator const nghttp3_settings() const;
84
85
    SET_NO_MEMORY_INFO()
86
    SET_MEMORY_INFO_NAME(Application::Options)
87
    SET_SELF_SIZE(Options)
88
89
    static v8::Maybe<Application_Options> From(Environment* env,
90
                                               v8::Local<v8::Value> value);
91
92
    std::string ToString() const;
93
94
    static const Application_Options kDefault;
95
  };
96
97
  // An Application implements the ALPN-protocol specific semantics on behalf
98
  // of a QUIC Session.
99
  class Application;
100
101
  // The ApplicationProvider optionally supplies the underlying application
102
  // protocol handler used by a session. The ApplicationProvider is supplied
103
  // in the *internal* options (that is, it is not exposed as a public, user
104
  // facing API. If the ApplicationProvider is not specified, then the
105
  // DefaultApplication is used (see application.cc).
106
  class ApplicationProvider : public BaseObject {
107
   public:
108
    using BaseObject::BaseObject;
109
    virtual std::unique_ptr<Application> Create(Session* session) = 0;
110
  };
111
112
  // The options used to configure a session. Most of these deal directly with
113
  // the transport parameters that are exchanged with the remote peer during
114
  // handshake.
115
  struct Options final : public MemoryRetainer {
116
    // The QUIC protocol version requested for the session.
117
    uint32_t version = NGTCP2_PROTO_VER_MAX;
118
119
    // Te minimum QUIC protocol version supported by this session.
120
    uint32_t min_version = NGTCP2_PROTO_VER_MIN;
121
122
    // By default a client session will ignore the preferred address
123
    // advertised by the the server. This option is only relevant for
124
    // client sessions.
125
    PreferredAddress::Policy preferred_address_strategy =
126
        PreferredAddress::Policy::IGNORE_PREFERRED;
127
128
    TransportParams::Options transport_params =
129
        TransportParams::Options::kDefault;
130
    TLSContext::Options tls_options = TLSContext::Options::kDefault;
131
132
    // A reference to the CID::Factory used to generate CID instances
133
    // for this session.
134
    const CID::Factory* cid_factory = &CID::Factory::random();
135
    // If the CID::Factory is a base object, we keep a reference to it
136
    // so that it cannot be garbage collected.
137
    BaseObjectPtr<BaseObject> cid_factory_ref;
138
139
    // If the application provider is specified, it will be used to create
140
    // the underlying Application instance for the session.
141
    BaseObjectPtr<ApplicationProvider> application_provider;
142
143
    // When true, QLog output will be enabled for the session.
144
    bool qlog = false;
145
146
    // The amount of time (in milliseconds) that the endpoint will wait for the
147
    // completion of the tls handshake.
148
    uint64_t handshake_timeout = UINT64_MAX;
149
150
    // Maximum initial flow control window size for a stream.
151
    uint64_t max_stream_window = 0;
152
153
    // Maximum initial flow control window size for the connection.
154
    uint64_t max_window = 0;
155
156
    // The max_payload_size is the maximum size of a serialized QUIC packet. It
157
    // should always be set small enough to fit within a single MTU without
158
    // fragmentation. The default is set by the QUIC specification at 1200. This
159
    // value should not be changed unless you know for sure that the entire path
160
    // supports a given MTU without fragmenting at any point in the path.
161
    uint64_t max_payload_size = kDefaultMaxPacketLength;
162
163
    // The unacknowledged_packet_threshold is the maximum number of
164
    // unacknowledged packets that an ngtcp2 session will accumulate before
165
    // sending an acknowledgement. Setting this to 0 uses the ngtcp2 defaults,
166
    // which is what most will want. The value can be changed to fine tune some
167
    // of the performance characteristics of the session. This should only be
168
    // changed if you have a really good reason for doing so.
169
    uint64_t unacknowledged_packet_threshold = 0;
170
171
    // There are several common congestion control algorithms that ngtcp2 uses
172
    // to determine how it manages the flow control window: RENO, CUBIC, and
173
    // BBR. The details of how each works is not relevant here. The choice of
174
    // which to use by default is arbitrary and we can choose whichever we'd
175
    // like. Additional performance profiling will be needed to determine which
176
    // is the better of the two for our needs.
177
    ngtcp2_cc_algo cc_algorithm = CC_ALGO_CUBIC;
178
179
    void MemoryInfo(MemoryTracker* tracker) const override;
180
    SET_MEMORY_INFO_NAME(Session::Options)
181
    SET_SELF_SIZE(Options)
182
183
    static v8::Maybe<Options> From(Environment* env,
184
                                   v8::Local<v8::Value> value);
185
186
    std::string ToString() const;
187
  };
188
189
  // The additional configuration settings used to create a specific session.
190
  // while the Options above can be used to configure multiple sessions, a
191
  // single Config is used to create a single session, which is why they are
192
  // kept separate.
193
  struct Config final : MemoryRetainer {
194
    // Is the Session acting as a client or a server?
195
    Side side;
196
197
    // The options to use for this session.
198
    Options options;
199
200
    // The actual QUIC version identified for this session.
201
    uint32_t version;
202
203
    SocketAddress local_address;
204
    SocketAddress remote_address;
205
206
    // The destination CID, identifying the remote peer. This value is always
207
    // provided by the remote peer.
208
    CID dcid = CID::kInvalid;
209
210
    // The source CID, identifying this session. This value is always created
211
    // locally.
212
    CID scid = CID::kInvalid;
213
214
    // Used only by client sessions to identify the original DCID
215
    // used to initiate the connection.
216
    CID ocid = CID::kInvalid;
217
    CID retry_scid = CID::kInvalid;
218
    CID preferred_address_cid = CID::kInvalid;
219
220
    ngtcp2_settings settings = {};
221
0
    operator ngtcp2_settings*() { return &settings; }
222
0
    operator const ngtcp2_settings*() const { return &settings; }
223
224
    Config(Environment* env,
225
           Side side,
226
           const Options& options,
227
           uint32_t version,
228
           const SocketAddress& local_address,
229
           const SocketAddress& remote_address,
230
           const CID& dcid,
231
           const CID& scid,
232
           const CID& ocid = CID::kInvalid);
233
234
    Config(Environment* env,
235
           const Options& options,
236
           const SocketAddress& local_address,
237
           const SocketAddress& remote_address,
238
           const CID& ocid = CID::kInvalid);
239
240
    void set_token(const uint8_t* token,
241
                   size_t len,
242
                   ngtcp2_token_type type = NGTCP2_TOKEN_TYPE_UNKNOWN);
243
    void set_token(const RetryToken& token);
244
    void set_token(const RegularToken& token);
245
246
    void MemoryInfo(MemoryTracker* tracker) const override;
247
    SET_MEMORY_INFO_NAME(Session::Config)
248
    SET_SELF_SIZE(Config)
249
250
    std::string ToString() const;
251
  };
252
253
  JS_CONSTRUCTOR(Session);
254
  JS_BINDING_INIT_BOILERPLATE();
255
256
  static BaseObjectPtr<Session> Create(
257
      Endpoint* endpoint,
258
      const Config& config,
259
      TLSContext* tls_context,
260
      const std::optional<SessionTicket>& ticket);
261
262
  // Really should be private but MakeDetachedBaseObject needs visibility.
263
  Session(Endpoint* endpoint,
264
          v8::Local<v8::Object> object,
265
          const Config& config,
266
          TLSContext* tls_context,
267
          const std::optional<SessionTicket>& ticket);
268
  DISALLOW_COPY_AND_MOVE(Session)
269
  ~Session() override;
270
271
  bool is_destroyed() const;
272
  bool is_server() const;
273
274
  uint32_t version() const;
275
  Endpoint& endpoint() const;
276
  TLSSession& tls_session() const;
277
  Application& application() const;
278
  const Config& config() const;
279
  const Options& options() const;
280
  const SocketAddress& remote_address() const;
281
  const SocketAddress& local_address() const;
282
283
  std::string diagnostic_name() const override;
284
285
  void MemoryInfo(MemoryTracker* tracker) const override;
286
  SET_MEMORY_INFO_NAME(Session)
287
  SET_SELF_SIZE(Session)
288
289
  operator ngtcp2_conn*() const;
290
291
  // Ensures that the session/application sends pending data when the scope
292
  // exits. Scopes can be nested. When nested, pending data will be sent
293
  // only when the outermost scope is exited.
294
  struct SendPendingDataScope final {
295
    Session* session;
296
    explicit SendPendingDataScope(Session* session);
297
    explicit SendPendingDataScope(const BaseObjectPtr<Session>& session);
298
    ~SendPendingDataScope();
299
    DISALLOW_COPY_AND_MOVE(SendPendingDataScope)
300
  };
301
302
  struct State;
303
  struct Stats;
304
305
  void HandleQlog(uint32_t flags, const void* data, size_t len);
306
307
 private:
308
  struct Impl;
309
310
  using StreamsMap = std::unordered_map<stream_id, BaseObjectPtr<Stream>>;
311
  using QuicConnectionPointer = DeleteFnPtr<ngtcp2_conn, ngtcp2_conn_del>;
312
313
  struct PathValidationFlags final {
314
    bool preferredAddress = false;
315
  };
316
317
  struct DatagramReceivedFlags final {
318
    bool early = false;
319
  };
320
321
  bool Receive(Store&& store,
322
               const SocketAddress& local_address,
323
               const SocketAddress& remote_address);
324
325
  void Send(const BaseObjectPtr<Packet>& packet);
326
  void Send(const BaseObjectPtr<Packet>& packet, const PathStorage& path);
327
  datagram_id SendDatagram(Store&& data);
328
329
  // A non-const variation to allow certain modifications.
330
  Config& config();
331
332
  enum class CreateStreamOption : uint8_t {
333
    NOTIFY,
334
    DO_NOT_NOTIFY,
335
  };
336
  BaseObjectPtr<Stream> FindStream(stream_id id) const;
337
  BaseObjectPtr<Stream> CreateStream(
338
      stream_id id,
339
      CreateStreamOption option = CreateStreamOption::NOTIFY,
340
      std::shared_ptr<DataQueue> data_source = nullptr);
341
  void AddStream(BaseObjectPtr<Stream> stream,
342
                 CreateStreamOption option = CreateStreamOption::NOTIFY);
343
  void RemoveStream(stream_id id);
344
  void ResumeStream(stream_id id);
345
  void StreamDataBlocked(stream_id id);
346
  void ShutdownStream(stream_id id, QuicError error = QuicError());
347
  void ShutdownStreamWrite(stream_id id, QuicError code = QuicError());
348
349
  // Use the configured CID::Factory to generate a new CID.
350
  CID new_cid(size_t len = CID::kMaxLength) const;
351
352
  const TransportParams local_transport_params() const;
353
  const TransportParams remote_transport_params() const;
354
355
  bool is_destroyed_or_closing() const;
356
  size_t max_packet_size() const;
357
  void set_priority_supported(bool on = true);
358
359
  // Open a new locally-initialized stream with the specified directionality.
360
  // If the session is not yet in a state where the stream can be openen --
361
  // such as when the handshake is not yet sufficiently far along and ORTT
362
  // session resumption is not being used -- then the stream will be created
363
  // in a pending state where actually opening the stream will be deferred.
364
  v8::MaybeLocal<v8::Object> OpenStream(
365
      Direction direction, std::shared_ptr<DataQueue> data_source = nullptr);
366
367
  void ExtendStreamOffset(stream_id id, size_t amount);
368
  void ExtendOffset(size_t amount);
369
  void SetLastError(QuicError&& error);
370
  uint64_t max_data_left() const;
371
372
  PendingStream::PendingStreamQueue& pending_bidi_stream_queue() const;
373
  PendingStream::PendingStreamQueue& pending_uni_stream_queue() const;
374
375
  // Implementation of SessionTicket::AppData::Source
376
  void CollectSessionTicketAppData(
377
      SessionTicket::AppData* app_data) const override;
378
  SessionTicket::AppData::Status ExtractSessionTicketAppData(
379
      const SessionTicket::AppData& app_data,
380
      SessionTicket::AppData::Source::Flag flag) override;
381
382
  // Returns true if the Session has entered the closing period after sending a
383
  // CONNECTION_CLOSE. While true, the Session is only permitted to transmit
384
  // CONNECTION_CLOSE frames until either the idle timeout period elapses or
385
  // until the Session is explicitly destroyed.
386
  bool is_in_closing_period() const;
387
388
  // Returns true if the Session has received a CONNECTION_CLOSE frame from the
389
  // peer. Once in the draining period, the Session is not permitted to send any
390
  // frames to the peer. The Session will be silently closed after either the
391
  // idle timeout period elapses or until the Session is explicitly destroyed.
392
  bool is_in_draining_period() const;
393
394
  // Returns false if the Session is currently in a state where it is unable to
395
  // transmit any packets.
396
  bool can_send_packets() const;
397
398
  // Returns false if the Session is currently in a state where it cannot create
399
  // new streams. Specifically, a stream is not in a state to create streams if
400
  // it has been destroyed or is closing.
401
  bool can_create_streams() const;
402
403
  // Returns false if the Session is currently in a state where it cannot open
404
  // a new locally-initiated stream. When using 0RTT session resumption, this
405
  // will become true immediately after the session ticket and transport params
406
  // have been configured. Otherwise, it becomes true after the remote transport
407
  // params and tx keys have been installed.
408
  bool can_open_streams() const;
409
410
  uint64_t max_local_streams_uni() const;
411
  uint64_t max_local_streams_bidi() const;
412
413
  bool wants_session_ticket() const;
414
  void SetStreamOpenAllowed();
415
416
  // It's a terrible name but "wrapped" here means that the Session has been
417
  // passed out to JavaScript and should be "wrapped" by whatever handler is
418
  // defined there to manage it.
419
  void set_wrapped();
420
421
  enum class CloseMethod : uint8_t {
422
    // Immediate close with a roundtrip through JavaScript, causing all
423
    // currently opened streams to be closed. An attempt will be made to
424
    // send a CONNECTION_CLOSE frame to the peer. If closing while within
425
    // the ngtcp2 callback scope, sending the CONNECTION_CLOSE will be
426
    // deferred until the scope exits.
427
    DEFAULT,
428
    // Same as DEFAULT except that no attempt to notify the peer will be
429
    // made.
430
    SILENT,
431
    // Closing gracefully disables the ability to open or accept new streams
432
    // for this Session. Existing streams are allowed to close naturally on
433
    // their own.
434
    // Once called, the Session will be immediately closed once there are no
435
    // remaining streams. No notification is given to the connected peer that
436
    // we are in a graceful closing state. A CONNECTION_CLOSE will be sent
437
    // only once FinishClose() is called.
438
    GRACEFUL
439
  };
440
  // Initiate closing of the session.
441
  void Close(CloseMethod method = CloseMethod::DEFAULT);
442
443
  void FinishClose();
444
  void Destroy();
445
446
  // Close the session and send a connection close packet to the peer.
447
  // If creating the packet fails the session will be silently closed.
448
  // The connection close packet will use the value of last_error_ as
449
  // the error code transmitted to the peer.
450
  void SendConnectionClose();
451
  void OnTimeout();
452
453
  void UpdateTimer();
454
  // Has to be called after certain operations that generate packets.
455
  void UpdatePacketTxTime();
456
  void UpdateDataStats();
457
  void UpdatePath(const PathStorage& path);
458
459
  void ProcessPendingBidiStreams();
460
  void ProcessPendingUniStreams();
461
462
  // JavaScript callouts
463
464
  void EmitClose(const QuicError& error = QuicError());
465
  void EmitDatagram(Store&& datagram, DatagramReceivedFlags flag);
466
  void EmitDatagramStatus(datagram_id id, DatagramStatus status);
467
  void EmitHandshakeComplete();
468
  void EmitKeylog(const char* line);
469
470
  struct ValidatedPath {
471
    std::shared_ptr<SocketAddress> local;
472
    std::shared_ptr<SocketAddress> remote;
473
  };
474
475
  void EmitPathValidation(PathValidationResult result,
476
                          PathValidationFlags flags,
477
                          const ValidatedPath& newPath,
478
                          const std::optional<ValidatedPath>& oldPath);
479
  void EmitSessionTicket(Store&& ticket);
480
  void EmitStream(const BaseObjectWeakPtr<Stream>& stream);
481
  void EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
482
                              const uint32_t* sv,
483
                              size_t nsv);
484
  void DatagramStatus(datagram_id datagramId, DatagramStatus status);
485
  void DatagramReceived(const uint8_t* data,
486
                        size_t datalen,
487
                        DatagramReceivedFlags flag);
488
  void GenerateNewConnectionId(ngtcp2_cid* cid, size_t len, uint8_t* token);
489
  bool HandshakeCompleted();
490
  void HandshakeConfirmed();
491
  void SelectPreferredAddress(PreferredAddress* preferredAddress);
492
493
  static std::unique_ptr<Application> SelectApplication(Session* session,
494
                                                        const Config& config);
495
496
  QuicConnectionPointer InitConnection();
497
498
  Side side_;
499
  ngtcp2_mem allocator_;
500
  std::unique_ptr<Impl> impl_;
501
  QuicConnectionPointer connection_;
502
  std::unique_ptr<TLSSession> tls_session_;
503
  BaseObjectPtr<LogStream> qlog_stream_;
504
  BaseObjectPtr<LogStream> keylog_stream_;
505
506
  friend class Application;
507
  friend class DefaultApplication;
508
  friend class Http3ApplicationImpl;
509
  friend class Endpoint;
510
  friend class Stream;
511
  friend class PendingStream;
512
  friend class TLSContext;
513
  friend class TLSSession;
514
  friend class TransportParams;
515
  friend struct Impl;
516
  friend struct SendPendingDataScope;
517
};
518
519
}  // namespace node::quic
520
521
#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS