summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/Http3Session.h
blob: c72f24959ec30c70a24f2cc9baea56a02cef25bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=4 sw=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef Http3Session_H__
#define Http3Session_H__

#include "HttpTrafficAnalyzer.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/net/NeqoHttp3Conn.h"
#include "nsAHttpConnection.h"
#include "nsDeque.h"
#include "nsISupportsImpl.h"
#include "nsITimer.h"
#include "nsIUDPSocket.h"
#include "nsRefPtrHashtable.h"
#include "nsTHashMap.h"
#include "nsWeakReference.h"

/*
 * WebTransport
 *
 * Http3Session and the underlying neqo code support multiplexing of multiple
 * WebTransport and multiplexing WebTransport sessions with regular HTTP
 * traffic. Whether WebTransport sessions are polled, will be controlled by the
 * nsHttpConnectionMgr.
 *
 * WebTransport support is negotiated using HTTP/3 setting. Before the settings
 * are available all WebTransport transactions are queued in
 * mWaitingForWebTransportNegotiation. The information on whether WebTransport
 * is supported is received via an HTTP/3 event. The event is
 * Http3Event::Tag::WebTransport  with the value
 * WebTransportEventExternal::Tag::Negotiated  that can be true or false. If
 * the WebTransport feature has been negotiated, queued transactions will be
 * activated otherwise they will be canceled(i.e. WebTransportNegotiationDone).
 *
 * The lifetime of a WebTransport session
 *
 * A WebTransport lifetime consists of 2 parts:
 * - WebTransport session setup
 * - WebTransport session active time
 *
 * WebTransport session setup:
 * A WebTransport session uses a regular HTTP request for negotiation.
 * Therefore when a new WebTransport is started a nsHttpChannel and the
 * corresponding nsHttpTransaction are created. The nsHttpTransaction is
 * dispatched to a Http3Session and after the
 * WebTransportEventExternal::Tag::Negotiated event it behaves almost the same
 * as a regular transaction, e.g. it is added to mStreamTransactionHash and
 * mStreamIdHash. For activating the session NeqoHttp3Conn::CreateWebTransport
 * is  called instead of NeqoHttp3Conn::Fetch(this is called for the regular
 * HTTP requests). In this phase, the WebTransport session is canceled in the
 * same way a regular request is canceled, by canceling the corresponding
 * nsHttpChannel. If HTTP/3 connection is closed in this phase the
 * corresponding nsHttpTransaction is canceled and this may cause the
 * transaction to be restarted (this is the existing restart logic) or the
 * error is propagated to the nsHttpChannel and its listener(via OnStartRequest
 * and OnStopRequest as the regular HTTP request).
 * The phase ends when a connection breaks or when the event
 * Http3Event::Tag::WebTransport with the value
 * WebTransportEventExternal::Tag::Session is received. The parameter
 * aData(from NeqoHttp3Conn::GetEvent) contain the HTTP head of the response.
 * The headers may be:
 * - failed code, i.e. anything except 200. In this case, the nsHttpTransaction
 *   behaves the same as a normal HTTP request and the nsHttpChannel listener
 *   will be informed via OnStartRequest and OnStopRequest calls.
 * - success code, i.e. 200. The code will be propagated to the
 *   nsHttpTransaction. The transaction will parse the header and call
 *   Http3Session::GetWebTransportSession. The function transfers WebTransport
 *   session into the next phase:
 *   - Removes nsHttpTransaction from mStreamTransactionHash.
 *   - Adds the stream to mWebTransportSessions
 *   - The nsHttpTransaction supplies Http3WebTransportSession to the
 *     WebTransportSessionProxy and vice versa.
 *     TODO remove this circular referencing.
 *
 * WebTransport session active time:
 * During this phase the following actions are possible:
 * - Cancelling a WebTransport session by the application:
 *   The application calls Http3WebTransportSession::CloseSession. This
 *   transfers Http3WebTransportSession into the CLOSE_PENDING state and calls
 *   Http3Session::ConnectSlowConsumer to add itself to the “ready for reading
 *   queue”. Consequently, the Http3Session will call
 *   Http3WebTransportSession::WriteSegments and
 *   Http3Session::CloseWebTransport will be called to send the closing signal
 *   to the peer. After this, the Http3WebTransportSession is in the state DONE
 *   and it will be removed from the Http3Session(the CloseStream function
 *   takes care of this).
 * - The peer sending a session closing signal:
 *   Http3Session will receive a Http3Event::Tag::WebTransport event with value
 *   WebTransportEventExternal::Tag::SessionClosed. The
 *   Http3WebTransportSession::OnSessionClosed function for the corresponding
 *   stream wil be called. The function will inform the corresponding
 *   WebTransportSessionProxy by calling OnSessionClosed function. The
 *   Http3WebTransportSession is in the state DONE and will be removed from the
 *   Http3Session(the CloseStream function takes care of this).
 */

namespace mozilla::net {

class HttpConnectionUDP;
class Http3StreamBase;
class QuicSocketControl;

// IID for the Http3Session interface
#define NS_HTTP3SESSION_IID                          \
  {                                                  \
    0x8fc82aaf, 0xc4ef, 0x46ed, {                    \
      0x89, 0x41, 0x93, 0x95, 0x8f, 0xac, 0x4f, 0x21 \
    }                                                \
  }

enum class EchExtensionStatus {
  kNotPresent,  // No ECH Extension was sent
  kGREASE,      // A GREASE ECH Extension was sent
  kReal         // A 'real' ECH Extension was sent
};

class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection {
 public:
  NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP3SESSION_IID)

  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSAHTTPTRANSACTION
  NS_DECL_NSAHTTPCONNECTION(mConnection)

  Http3Session();
  nsresult Init(const nsHttpConnectionInfo* aConnInfo, nsINetAddr* selfAddr,
                nsINetAddr* peerAddr, HttpConnectionUDP* udpConn,
                uint32_t controlFlags, nsIInterfaceRequestor* callbacks);

  bool IsConnected() const { return mState == CONNECTED; }
  bool CanSendData() const {
    return (mState == CONNECTED) || (mState == ZERORTT);
  }
  bool IsClosing() const { return (mState == CLOSING || mState == CLOSED); }
  bool IsClosed() const { return mState == CLOSED; }

  bool AddStream(nsAHttpTransaction* aHttpTransaction, int32_t aPriority,
                 nsIInterfaceRequestor* aCallbacks);

  bool CanReuse();

  // The following functions are used by Http3Stream and
  // Http3WebTransportSession:
  nsresult TryActivating(const nsACString& aMethod, const nsACString& aScheme,
                         const nsACString& aAuthorityHeader,
                         const nsACString& aPath, const nsACString& aHeaders,
                         uint64_t* aStreamId, Http3StreamBase* aStream);
  // The folowing functions are used by Http3Stream:
  void CloseSendingSide(uint64_t aStreamId);
  nsresult SendRequestBody(uint64_t aStreamId, const char* buf, uint32_t count,
                           uint32_t* countRead);
  nsresult ReadResponseHeaders(uint64_t aStreamId,
                               nsTArray<uint8_t>& aResponseHeaders, bool* aFin);
  nsresult ReadResponseData(uint64_t aStreamId, char* aBuf, uint32_t aCount,
                            uint32_t* aCountWritten, bool* aFin);

  // The folowing functions are used by Http3WebTransportSession:
  nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError,
                             const nsACString& aMessage);
  nsresult CreateWebTransportStream(uint64_t aSessionId,
                                    WebTransportStreamType aStreamType,
                                    uint64_t* aStreamId);
  void CloseStream(Http3StreamBase* aStream, nsresult aResult);
  void CloseStreamInternal(Http3StreamBase* aStream, nsresult aResult);

  void SetCleanShutdown(bool aCleanShutdown) {
    mCleanShutdown = aCleanShutdown;
  }

  bool TestJoinConnection(const nsACString& hostname, int32_t port);
  bool JoinConnection(const nsACString& hostname, int32_t port);

  void TransactionHasDataToWrite(nsAHttpTransaction* caller) override;
  void TransactionHasDataToRecv(nsAHttpTransaction* caller) override;
  [[nodiscard]] nsresult GetTransactionTLSSocketControl(
      nsITLSSocketControl**) override;

  // This function will be called by QuicSocketControl when the certificate
  // verification is done.
  void Authenticated(int32_t aError);

  nsresult ProcessOutputAndEvents(nsIUDPSocket* socket);

  void ReportHttp3Connection();

  int64_t GetBytesWritten() { return mTotalBytesWritten; }
  int64_t BytesRead() { return mTotalBytesRead; }

  nsresult SendData(nsIUDPSocket* socket);
  nsresult RecvData(nsIUDPSocket* socket);

  void DoSetEchConfig(const nsACString& aEchConfig);

  nsresult SendPriorityUpdateFrame(uint64_t aStreamId, uint8_t aPriorityUrgency,
                                   bool aPriorityIncremental);

  void ConnectSlowConsumer(Http3StreamBase* stream);

  nsresult TryActivatingWebTransportStream(uint64_t* aStreamId,
                                           Http3StreamBase* aStream);
  void CloseWebTransportStream(Http3WebTransportStream* aStream,
                               nsresult aResult);
  void StreamHasDataToWrite(Http3StreamBase* aStream);
  void ResetWebTransportStream(Http3WebTransportStream* aStream,
                               uint64_t aErrorCode);
  void StreamStopSending(Http3WebTransportStream* aStream, uint8_t aErrorCode);

  void SendDatagram(Http3WebTransportSession* aSession,
                    nsTArray<uint8_t>& aData, uint64_t aTrackingId);

  uint64_t MaxDatagramSize(uint64_t aSessionId);

  void SetSendOrder(Http3StreamBase* aStream, Maybe<int64_t> aSendOrder);

  void CloseWebTransportConn();

 private:
  ~Http3Session();

  void CloseInternal(bool aCallNeqoClose);
  void Shutdown();

  bool RealJoinConnection(const nsACString& hostname, int32_t port,
                          bool justKidding);

  nsresult ProcessOutput(nsIUDPSocket* socket);
  void ProcessInput(nsIUDPSocket* socket);
  nsresult ProcessEvents();

  nsresult ProcessTransactionRead(uint64_t stream_id);
  nsresult ProcessTransactionRead(Http3StreamBase* stream);
  nsresult ProcessSlowConsumers();

  void SetupTimer(uint64_t aTimeout);

  enum ResetType {
    RESET,
    STOP_SENDING,
  };
  void ResetOrStopSendingRecvd(uint64_t aStreamId, uint64_t aError,
                               ResetType aType);

  void QueueStream(Http3StreamBase* stream);
  void RemoveStreamFromQueues(Http3StreamBase*);
  void ProcessPending();

  void CallCertVerification(Maybe<nsCString> aEchPublicName);
  void SetSecInfo();

  void EchOutcomeTelemetry();

  void StreamReadyToWrite(Http3StreamBase* aStream);
  void MaybeResumeSend();

  void CloseConnectionTelemetry(CloseError& aError, bool aClosing);
  void Finish0Rtt(bool aRestart);

  enum ZeroRttOutcome {
    NOT_USED,
    USED_SUCCEEDED,
    USED_REJECTED,
    USED_CONN_ERROR,
    USED_CONN_CLOSED_BY_NECKO
  };
  void ZeroRttTelemetry(ZeroRttOutcome aOutcome);

  RefPtr<NeqoHttp3Conn> mHttp3Connection;
  RefPtr<nsAHttpConnection> mConnection;
  // We need an extra map to store the mapping of WebTransportSession and
  // WebTransportStreams to handle the case that a stream is already removed
  // from mStreamIdHash and we still need the WebTransportSession.
  nsTHashMap<nsUint64HashKey, uint64_t> mWebTransportStreamToSessionMap;
  nsRefPtrHashtable<nsUint64HashKey, Http3StreamBase> mStreamIdHash;
  nsRefPtrHashtable<nsPtrHashKey<nsAHttpTransaction>, Http3StreamBase>
      mStreamTransactionHash;

  nsRefPtrDeque<Http3StreamBase> mReadyForWrite;

  nsTArray<RefPtr<Http3StreamBase>> mSlowConsumersReadyForRead;
  nsRefPtrDeque<Http3StreamBase> mQueuedStreams;

  enum State {
    INITIALIZING,
    ZERORTT,
    CONNECTED,
    CLOSING,
    CLOSED
  } mState{INITIALIZING};

  bool mAuthenticationStarted{false};
  bool mCleanShutdown{false};
  bool mGoawayReceived{false};
  bool mShouldClose{false};
  bool mIsClosedByNeqo{false};
  bool mHttp3ConnectionReported = false;
  // mError is neqo error (a protocol error) and that may mean that we will
  // send some packets after that.
  nsresult mError{NS_OK};
  // This is a socket error, there is no poioint in sending anything on that
  // socket.
  nsresult mSocketError{NS_OK};
  bool mBeforeConnectedError{false};
  uint64_t mCurrentBrowserId;

  // True if the mTimer is inited and waiting for firing.
  bool mTimerActive{false};

  RefPtr<HttpConnectionUDP> mUdpConn;

  // The underlying socket transport object is needed to propogate some events
  RefPtr<nsISocketTransport> mSocketTransport;

  nsCOMPtr<nsITimer> mTimer;

  nsTHashMap<nsCStringHashKey, bool> mJoinConnectionCache;

  RefPtr<QuicSocketControl> mSocketControl;

  uint64_t mTransactionCount = 0;

  // The stream(s) that we are getting 0RTT data from.
  nsTArray<WeakPtr<Http3StreamBase>> m0RTTStreams;
  // The stream(s) that are not able to send 0RTT data. We need to
  // remember them put them into mReadyForWrite queue when 0RTT finishes.
  nsTArray<WeakPtr<Http3StreamBase>> mCannotDo0RTTStreams;

  // The following variables are needed for telemetry.
  TimeStamp mConnectionIdleStart;
  TimeStamp mConnectionIdleEnd;
  Maybe<uint64_t> mFirstStreamIdReuseIdleConnection;
  TimeStamp mTimerShouldTrigger;
  TimeStamp mZeroRttStarted;
  uint64_t mBlockedByStreamLimitCount = 0;
  uint64_t mTransactionsBlockedByStreamLimitCount = 0;
  uint64_t mTransactionsSenderBlockedByFlowControlCount = 0;

  // NS_NET_STATUS_CONNECTED_TO event will be created by the Http3Session.
  // We want to  propagate it to the first transaction.
  RefPtr<nsHttpTransaction> mFirstHttpTransaction;

  RefPtr<nsHttpConnectionInfo> mConnInfo;

  bool mThroughCaptivePortal = false;
  int64_t mTotalBytesRead = 0;     // total data read
  int64_t mTotalBytesWritten = 0;  // total data read
  PRIntervalTime mLastWriteTime = 0;

  // Records whether we sent an ECH Extension and whether it was a GREASE Xtn
  EchExtensionStatus mEchExtensionStatus = EchExtensionStatus::kNotPresent;

  // Records whether the handshake finished successfully and we established a
  // a connection.
  bool mHandshakeSucceeded = false;

  nsCOMPtr<nsINetAddr> mNetAddr;

  enum WebTransportNegotiation { DISABLED, NEGOTIATING, FAILED, SUCCEEDED };
  WebTransportNegotiation mWebTransportNegotiationStatus{
      WebTransportNegotiation::DISABLED};

  nsTArray<WeakPtr<Http3StreamBase>> mWaitingForWebTransportNegotiation;
  // 1795854 implement the case when WebTransport is not supported.
  // Also, implement the case when the  HTTP/3 session fails before settings
  // are exchanged.
  void WebTransportNegotiationDone();

  nsTArray<RefPtr<Http3StreamBase>> mWebTransportSessions;
  nsTArray<RefPtr<Http3StreamBase>> mWebTransportStreams;

  bool mHasWebTransportSession = false;
  // When true, we don't add this connection info into the Http/3 excluded list.
  bool mDontExclude = false;
  // The lifetime of the UDP socket is managed by the HttpConnectionUDP. This
  // is only used in Http3Session::ProcessOutput. Using raw pointer here to
  // improve performance.
  nsIUDPSocket* mSocket;
};

NS_DEFINE_STATIC_IID_ACCESSOR(Http3Session, NS_HTTP3SESSION_IID);

}  // namespace mozilla::net

#endif  // Http3Session_H__