-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathQUICConnection.swift
More file actions
6206 lines (5569 loc) · 241 KB
/
Copy pathQUICConnection.swift
File metadata and controls
6206 lines (5569 loc) · 241 KB
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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if !NETWORK_NO_SWIFT_QUIC
#if canImport(BasicContainers)
import BasicContainers
internal import DequeModule
#endif
#if canImport(Glibc)
import Glibc
internal import Logging
#elseif canImport(Musl)
import Musl
internal import Logging
#elseif canImport(os)
internal import os
#endif
#if IMPORT_SWIFTTLS && canImport(SwiftTLS)
#if EXPORT_SWIFTTLS
@_spi(SwiftTLSOptions) @_spi(SwiftTLSProtocol) import SwiftTLS
#else
@_spi(SwiftTLSOptions) @_spi(SwiftTLSProtocol) @_weakLinked internal import SwiftTLS
#endif
#endif
@_spi(ProtocolProvider)
@available(Network 0.1.0, *)
public enum QUICConnectionState: CustomStringConvertible {
case invalid
// Server only when parsing
case idle
// Server
case versionSent
case retrySent
case initialReceived
case initialProcessed
// Client
case versionReceived
case initialSent
case retryReceived
// Client & Server
case handshake
case connected
// Termination
case closing
case draining
public var description: String {
switch self {
case .invalid: return "invalid"
case .idle: return "idle"
case .versionSent: return "versionSent"
case .retrySent: return "retrySent"
case .initialReceived: return "initialReceived"
case .initialProcessed: return "initialProcessed"
case .versionReceived: return "versionReceived"
case .initialSent: return "initialSent"
case .retryReceived: return "retryReceived"
case .handshake: return "handshake"
case .connected: return "connected"
case .closing: return "closing"
case .draining: return "draining"
}
}
func isValidStateChange(to newState: QUICConnectionState, logIDString: String) -> Bool {
switch (self, newState) {
case (.invalid, .idle),
(.idle, .initialReceived),
(.idle, .initialSent),
(.idle, .retrySent),
(.idle, .versionSent),
(.versionSent, .retrySent),
(.versionSent, .closing),
(.versionSent, .draining),
(.versionSent, .initialReceived),
(.versionReceived, .closing),
(.versionReceived, .initialSent),
(.versionReceived, .draining),
(.initialSent, .versionReceived),
(.initialSent, .retryReceived),
(.initialSent, .handshake),
(.initialSent, .closing),
(.initialSent, .draining),
(.initialReceived, .initialProcessed),
(.initialReceived, .handshake),
(.initialReceived, .closing),
(.initialReceived, .draining),
(.initialProcessed, .handshake),
(.initialProcessed, .closing),
(.initialProcessed, .draining),
(.retryReceived, .initialSent),
(.retryReceived, .draining),
(.retrySent, .initialReceived),
(.retrySent, .draining),
(.handshake, .connected),
(.handshake, .closing),
(.handshake, .draining),
(.connected, .closing),
(.connected, .draining),
(.closing, .draining):
return true
default:
Logger.proto.fault(
"\(logIDString) Invalid connection state transition: \(self) -> \(newState)"
)
return false
}
}
mutating func change(to newState: QUICConnectionState, logIDString: String) {
#if !DisableDebugLogging
let loggableSelf = self
Logger.proto.debug(
"\(logIDString) connection state transition: \(loggableSelf) -> \(newState)"
)
#endif
_ = isValidStateChange(to: newState, logIDString: logIDString)
self = newState
}
var isTerminal: Bool {
switch self {
case .closing, .draining:
return true
default:
return false
}
}
var isConnected: Bool {
switch self {
case .connected, .closing, .draining:
return true
default:
return false
}
}
}
@_spi(ProtocolProvider)
@available(Network 0.1.0, *)
public final class QUICConnection: ManyToManyApplicationStreamProtocol,
ManyToManyApplicationDatagramProtocol, ManyToManyOutboundDatagramProtocol,
StreamListenerHandler, HeterogeneousManyToManyProtocolHandler, TimerSchedulable,
ProtocolInstanceContainer
{
public var inboundFlowLinkage = InboundStreamFlowLinkage()
public var secondaryInboundFlowLinkage = InboundDatagramFlowLinkage()
public var multiplexedFlows = [MultiplexedFlowIdentifier: QUICStreamInstance]()
public var multiplexedSecondaryFlows = [MultiplexedFlowIdentifier: QUICDatagramFlow]()
public var multiplexingPaths = [MultiplexingPathIdentifier: QUICPath]()
public typealias Flow = QUICStreamInstance
public typealias UpperProtocol = InboundStreamFlowLinkage
public typealias SecondaryFlow = QUICDatagramFlow
public typealias SecondaryUpperProtocol = InboundDatagramFlowLinkage
public typealias Path = QUICPath
public var reference: ProtocolInstanceReference { ProtocolInstanceReference(quic: self) }
public var log = NetworkLoggerState()
var logPrefixer: LogPrefixer
public private(set) var context: NetworkContext
public var eventManager = ProtocolEventManager()
public var state: QUICConnectionState = .invalid
var flowControlState = FlowControlState(isStream: false)
// All the CIDs advertised to the peer
private(set) var localCIDs = QUICConnectionIDList()
private var nextLocalCIDSequenceNumber: UInt64 = 1
private var largestSentLocalCIDSequenceNumber: UInt64 = 1
// All the CIDs advertised by the peer
var remoteCIDs = QUICConnectionIDList()
// The largest "retire prior to" value received
private var retiredRemoteCIDSequenceNumberThreshold: UInt64 = 0
private(set) var remoteTransportParametersForEarlyData = false
private(set) var remoteTransportParameters: TransportParameters?
private(set) var localTransportParameters: TransportParameters
private(set) var connectionMetadata = QUICConnectionProtocol.QUICConnectionMetadata()
private(set) var packetParser: PacketParser
private(set) var keyState = PacketKeyState.initial
var remoteMaxDatagramFrameSize = 0
var remoteMaximumUDPPayloadSize = 0
var timer: Timer
private(set) var ack: Ack
private(set) var ecn: ECN
var recovery: Recovery
private(set) var migration = Migration()
private(set) var crypto: QUICCrypto
var protector: Protector
// Values for recovery that need to be stored on the main connection
private var largestAckedInitialPacketNumber: PacketNumber = .none
private var largestAckedHandshakePacketNumber: PacketNumber = .none
private var largestAckedApplicationPacketNumber: PacketNumber = .none
func largestAckedPacketNumber(space: PacketNumberSpace) -> PacketNumber {
switch space {
case .initial: return largestAckedInitialPacketNumber
case .handshake: return largestAckedHandshakePacketNumber
case .applicationData: return largestAckedApplicationPacketNumber
}
}
func setLargestAckedPacketNumber(_ number: PacketNumber, space: PacketNumberSpace) {
switch space {
case .initial: largestAckedInitialPacketNumber = number
case .handshake: largestAckedHandshakePacketNumber = number
case .applicationData: largestAckedApplicationPacketNumber = number
}
}
private(set) var handshakeStartTime: NetworkClock.Instant = .zero
private(set) var handshakeDuration: NetworkDuration = .zero
private(set) var handshakeRTT: NetworkDuration = .zero
private(set) var idleTimeout: NetworkDuration = .zero
var keepaliveDuration: NetworkDuration = .zero
var keepaliveTimerID: Timer.TimerID?
var maxKeepaliveCount = 0
var unackedKeepaliveCount = 0
var currentInboundReceiveTimestamp: NetworkClock.Instant?
var currentSendTimestamp: NetworkClock.Instant?
@_optimize(speed)
@inline(__always)
var now: NetworkClock.Instant {
if let currentInboundReceiveTimestamp {
return currentInboundReceiveTimestamp
} else if let currentSendTimestamp {
return currentSendTimestamp
} else {
return NetworkClock.Instant.now
}
}
var lastPacketReceivedTimestamp: NetworkClock.Instant = .zero
var lastAckElicitingPacketSentTimestamp: NetworkClock.Instant = .zero
var lastShorthandTimestamp: NetworkClock.Instant = .zero
var idleTimerID: Timer.TimerID?
var logIDNumber: Int = 0
let signpostID = QUICSignpost.makeSignpostID()
var signpostConnectInterval: QUICSignpost.IntervalState?
// Initial version set by client or server
var initialVersion: QUICVersion? = Constants.defaultVersion
// The final QUIC version negotiated
var negotiatedVersion: QUICVersion?
// Used to return the most up to date version
public var currentVersion: QUICVersion {
// If negotiated version is set then this is the final version
if let negotiatedVersion {
return negotiatedVersion
}
return initialVersion ?? .v1
}
public var forceUnsupportedClientVersion: Bool = false
// MARK: Streams
var unidirectionalStreams = QUICStreamIDState(.unidirectional)
var bidirectionalStreams = QUICStreamIDState(.bidirectional)
private(set) var zombieStreamList = QUICStreamZombieList()
// List of streams that have app input data in their reassembly queue.
var pendingReassemblyDequeue = QUICStreamList.pendingReassemblyDequeueList()
private(set) var knownFlows = [QUICStreamID: MultiplexedFlowIdentifier]()
private(set) var localCIDLength: Int = 0
private var initialSourceConnectionID: QUICConnectionID?
private var initialStatelessResetToken: QUICStatelessResetToken?
private var disableAutomaticNewConnectionIDs = false
private var resendRejectedEarlyDataAutomatically = false
private(set) var allowPMTUD = false
private(set) var pmtudIgnoreCost = false
private(set) var pmtudInterval: NetworkDuration? = nil
private(set) var earlyDataAccepted = false
private var drainingScheduled = false
fileprivate var flowsHaveEverMarkedIdle = false
// MARK: Path
var currentPath: QUICPath?
private(set) var initialMSS = Constants.initialMSS
private(set) var pathPropertiesMTU = 0
var isPacing: Bool = false
// MARK: Logging
public var qlogConfiguration: QLogConfiguration?
private(set) var qLog: QLog?
var stats: Statistics
var logIDString: String { self.log.logPrefix }
var initialInterface: Interface? = nil
// MARK: Flags
private(set) var isServer = false
private(set) var isCancelled = false
var hasSentDataBlocked = false
private(set) var versionReceived = false
private(set) var retryReceived = false
private var retrySCID: QUICConnectionID?
private(set) var spinBitEnabled = false
private(set) var autoReceivedBuffer = false
private(set) var outboundDataPending = false
private(set) var trafficManagementBackground = false
private(set) var initialKeysDiscarded = false
private(set) var receivedHandshakePacket = false
var discardCryptoFrames = false
private(set) var initialSpinValue = false
private(set) var retryEnabled = false
var earlyDataSignalled = false
private(set) var inError = false
var hasAdvertisedMaxData = false
private(set) var waitingForOutstandingKeepAliveAcks = false
private(set) var tlsOptions: SwiftTLSProtocol.Options?
private(set) var testSendingShortPackets = false
private(set) var migrationSupported = false
// false == IPv6, true == IPv4
private(set) var initialAddressIsIPv4 = false
#if NETWORK_EMBEDDED
let isL4SEnabled = false
#else
private(set) var isL4SEnabled = false
#endif
private(set) var isHandshakeConfirmed = false
public var pacingEnabled: Bool = false
private(set) var datagramUseQuarterStreamID = false
var datagramUseContextID = false
var datagramEnableFlowID = false
private(set) var maximumConcurrentBidirectionalStreams: Int?
private(set) var maximumConcurrentUnidirectionalStreams: Int?
private var originalDCID: QUICConnectionID
var initialDCID: QUICConnectionID?
var initialToken: [UInt8]?
var newToken: [UInt8]?
var closeFrameType: FrameType?
// The error code and reason sent/received in a CONNECTION_CLOSE frame.
public var closeError: QUICTransportError?
var receivedConnectionClose = false
// The error code and reason sent/received in an APPLICATION_CLOSE frame.
public var applicationCloseError: QUICApplicationError?
var receivedApplicationClose = false
// The error to be reported to app when draining or
// closing the connection.
var errorToReport: NetworkError?
func withCurrentPath(_ block: (borrowing QUICPath) -> Void) {
guard let currentPath else { return }
block(currentPath)
}
func withCurrentPath(_ block: (borrowing QUICPath) -> Bool) -> Bool {
guard let currentPath else { return false }
return block(currentPath)
}
public init(context: NetworkContext) {
self.context = context
self.logPrefixer = LogPrefixer("[C?]")
self.packetParser = PacketParser(logPrefixer: self.logPrefixer)
ack = Ack(logPrefixer: self.logPrefixer)
let defaultServerCIDLength = 8
let dcid = QUICConnectionID(defaultServerCIDLength)
originalDCID = dcid
protector = Protector(isClient: true, destinationCID: dcid, logPrefixer: self.logPrefixer)
crypto = QUICCrypto(context: context)
self.recovery = Recovery(logPrefixer: self.logPrefixer)
self.localTransportParameters = TransportParameters(logPrefixer: self.logPrefixer)
self.timer = Timer(logPrefixer: self.logPrefixer)
self.ecn = ECN()
self.stats = Statistics()
}
public func setup(
remote: Endpoint?,
local: Endpoint?,
parameters: Parameters?,
path: PathProperties?
) throws(NetworkError) {
self.isServer = parameters?.isServer ?? false
self.logPrefixer.log.logPrefix = self.log.logPrefix
self.initialInterface = path?.directInterface ?? nil
self.logPrefixer.logIDString = ""
state.change(to: .idle, logIDString: logPrefixer.logIDString)
// Setup metadata callbacks
self.setMetadataHandlers()
self.timer = Timer(reference: self.reference, logPrefixer: logPrefixer)
let ackTimerID = timer.insert(description: "ACK") {
self.ack.timerFired(timeNow: .now)
}
self.ack = Ack(connection: self, timerID: ackTimerID, logPrefixer: logPrefixer)
let recoveryTimerID = timer.insert(description: "Recovery") {
self.recovery.timerFired(timeNow: .now)
}
self.recovery = Recovery(
connection: self,
timerID: recoveryTimerID,
logPrefixer: logPrefixer
)
migration.timerID = timer.insert(description: "Migration") {
self.migration.timerFired(connection: self)
}
if let remote, case .address(let remoteAddress) = remote.type,
case .v4 = remoteAddress.type
{
initialAddressIsIPv4 = true
}
if let path {
pathPropertiesMTU = path.mtu
}
if let parameters,
let quicOptions = quicOptions(from: parameters, for: .allFlows),
let protocolOptions = quicOptions.perProtocolOptions
{
self.logIDNumber = quicOptions.logIDNumber ?? 0
var enableECN = true
var enableECNEcho = true
var enableL4s: Bool? = nil // Default
enableECN = !protocolOptions.quicConnectionOptions.disableECN
enableECNEcho = !protocolOptions.quicConnectionOptions.disableECNEcho
enableL4s = protocolOptions.quicConnectionOptions.enableL4S
if isServer {
retryEnabled = protocolOptions.quicConnectionOptions.retry
// Stateless Reset Token
let statelessResetToken = TransportParameter.statelessResetToken(
statelessResetToken: QUICStatelessResetToken()
)
localTransportParameters.append(statelessResetToken)
} else {
// A client can force version negotiation by setting the initial version to the negotiationPattern (0x?a?a?a?a)
if protocolOptions.quicConnectionOptions.forceVersionNegotiation {
self.initialVersion = .negotiationPattern
log.info("Setting negotiation pattern")
}
}
if let initialSCID = protocolOptions.quicConnectionOptions.initialSourceConnectionID {
initialSourceConnectionID = initialSCID
}
if let statelessResetToken = protocolOptions.quicConnectionOptions
.initialStatelessResetToken
{
initialStatelessResetToken = statelessResetToken
}
disableAutomaticNewConnectionIDs =
protocolOptions.quicConnectionOptions.disableAutomaticNewConnectionIDs
resendRejectedEarlyDataAutomatically =
protocolOptions.quicConnectionOptions.resendRejectedEarlyDataAutomatically
if protocolOptions.quicConnectionOptions.keepaliveCount > 0 {
maxKeepaliveCount = Int(protocolOptions.quicConnectionOptions.keepaliveCount)
}
allowPMTUD = protocolOptions.quicConnectionOptions.pmtud
pmtudIgnoreCost = protocolOptions.quicConnectionOptions.pmtudIgnoreCost
pmtudInterval = protocolOptions.quicConnectionOptions.pmtudUpdateInterval
pacingEnabled = protocolOptions.quicConnectionOptions.enablePacing
testSendingShortPackets =
protocolOptions.quicConnectionOptions.testSendingShortPackets
forceUnsupportedClientVersion = protocolOptions.quicConnectionOptions.forceUnsupportedClientVersion
qlogConfiguration = protocolOptions.quicConnectionOptions.qlogConfiguration
datagramUseQuarterStreamID =
protocolOptions.quicConnectionOptions.datagramQuarterStreamID
datagramUseContextID = protocolOptions.quicConnectionOptions.datagramContextID
datagramEnableFlowID = protocolOptions.quicConnectionOptions.datagramEnableFlowID
maximumConcurrentBidirectionalStreams =
protocolOptions.quicConnectionOptions.maximumConcurrentBidirectionalStreams
maximumConcurrentUnidirectionalStreams =
protocolOptions.quicConnectionOptions.maximumConcurrentUnidirectionalStreams
if let currentPath {
currentPath.pacePackets = pacingEnabled
currentPath.setupL4SState(l4sEnabled: enableL4s)
if currentPath.l4sEnabled && QUICPreferences.shared.ackCompressionEnabled {
// Disable ACK compression when L4S is enabled.
ack.disableAckCompression = true
}
}
if !protocolOptions.quicConnectionOptions.disableSpinBit {
// RFC9000: "Even when the spin bit is not disabled by
// the administrator, endpoints MUST disable their use
// of the spin bit for a random selection of at least
// one in every 16 network paths, or for one in every
// 16 connection IDs, in order to ensure that QUIC
// connections that disable the spin bit are commonly
// observed on the network."
var randomNumberGenerator = SystemRandomNumberGenerator()
spinBitEnabled = UInt8.random(in: 0..<16, using: &randomNumberGenerator) > 0
if !spinBitEnabled {
// It's recommended that the spin value is set
// to a random value when we are not using the spin bit.
initialSpinValue = UInt8.random(in: 0...1) > 0
}
} else {
// The application has asked us to disable the spin bit.
spinBitEnabled = false
initialSpinValue = protocolOptions.quicConnectionOptions.spinBitValue
}
// Setup ECN
ecn = ECN(
echoEnabled: enableECNEcho,
markingEnabled: enableECN,
l4sEnabled: enableL4s,
connection: self,
logPrefixer: self.logPrefixer
)
setupLocalTransportParameters(quicOptions: quicOptions)
tlsOptions = quicOptions.tlsOptions
}
#if QlogOutput
// There are 2 ways to setup a Qlog directory, one through the configuration and one through Preferences
if qlogConfiguration == nil && QUICPreferences.shared.quiclogDirectory != "" {
qlogConfiguration = QLogConfiguration(logPath: QUICPreferences.shared.quiclogDirectory)
}
// Only setup qlog if the directory is set
if let qlogConfiguration {
self.qLog = QLog(configuration: qlogConfiguration)
log.info("qlog setup with configuration: \(qlogConfiguration)")
}
#endif
log.info("Setup QUIC connection (spin bit \(spinBitEnabled ? "enabled" : "disabled"))")
}
public func setup(
flow flowID: MultiplexedFlowIdentifier,
remote: Endpoint?,
local: Endpoint?,
parameters: Parameters?,
path: PathProperties?
) throws(NetworkError) {
guard let parameters,
let quicOptions = quicOptions(from: parameters, for: flowID),
let protocolOptions = quicOptions.perProtocolOptions
else {
throw NetworkError.posix(EINVAL)
}
if protocolOptions.isDatagram {
guard let datagramFlow = secondaryFlow(for: flowID) else {
throw NetworkError.posix(ENOENT)
}
setupNewDatagramFlow(datagramFlow, with: protocolOptions)
} else {
guard let stream = flow(for: flowID) else {
throw NetworkError.posix(ENOENT)
}
setupNewOutboundStream(stream, with: protocolOptions)
}
}
deinit {
self.qLog = nil
}
func setMSS(_ newMSS: Int, on path: QUICPath) {
if _slowPath(newMSS < Constants.initialMSS) {
path.mss = Constants.initialMSS
} else if path.maximumMSS > Constants.initialMSS && newMSS > path.maximumMSS {
path.mss = path.maximumMSS
} else if newMSS < path.initialMSS {
path.mss = path.initialMSS
} else {
path.mss = newMSS
}
path.congestionControlMSSChanged(mss: path.mss)
if path == currentPath {
applyToAllSecondaryFlows { datagramFlow in
datagramFlow.updateUsableDatagramFrameSize(connection: self, path: path)
}
}
}
func setInitialMSS(on path: QUICPath) {
setMSS(initialMSS, on: path)
}
func updateMaxBidirectionalStreamsFromApplication(_ maximumStreams: Int) {
let newMaxStreams = max(maximumStreams, self.bidirectionalStreams.localMaxStreams)
if newMaxStreams > Constants.maxStreamLimit {
self.close(with: .streamLimitError, "MAX_STREAMS value over limit")
self.log.error("Received MAX_STREAMS value too large: \(maximumStreams)")
return
}
log.notice("Advertising MAX_STREAMS bidi: \(newMaxStreams)")
self.withMutableQUICStreams(unidirectional: false) {
mutableStreamsState in
mutableStreamsState.updateLocalMaxStreams(
server: self.isServer,
newMaxStreams: Int(newMaxStreams),
logIDString: self.logPrefixer.logIDString
)
}
self.sendMaxStreamsBidirectional()
// Trigger sending, since this is an otherwise external event
self.sendFrames()
}
func updateMaxUnidirectionalStreamsFromApplication(_ maximumStreams: Int) {
let newMaxStreams = max(maximumStreams, self.unidirectionalStreams.localMaxStreams)
if newMaxStreams > Constants.maxStreamLimit {
self.close(with: .streamLimitError, "MAX_STREAMS value over limit")
self.log.error("Received MAX_STREAMS value too large: \(maximumStreams)")
return
}
log.notice("Advertising MAX_STREAMS uni: \(newMaxStreams)")
self.withMutableQUICStreams(unidirectional: true) {
mutableStreamsState in
mutableStreamsState.updateLocalMaxStreams(
server: self.isServer,
newMaxStreams: Int(newMaxStreams),
logIDString: self.logPrefixer.logIDString
)
}
self.sendMaxStreamsUnidirectional()
// Trigger sending, since this is an otherwise external event
self.sendFrames()
}
func setMetadataHandlers() {
// Set handlers
#if !NETWORK_EMBEDDED
// Setup the local_max_streams_bidirectional_handler
self.connectionMetadata.setLocalMaxStreamsBidirectional { maxStreams in
self.updateMaxBidirectionalStreamsFromApplication(Int(maxStreams))
}
self.connectionMetadata.setLocalMaxStreamsUnidirectional { (maxStreams: UInt64) in
self.updateMaxUnidirectionalStreamsFromApplication(Int(maxStreams))
}
self.connectionMetadata.setKeepalive { (keepaliveSeconds: UInt16) in
if keepaliveSeconds == Constants.defaultKeepaliveValue {
self.keepaliveConfigure(duration: Constants.defaultKeepaliveDuration)
} else {
self.keepaliveConfigure(duration: .seconds(keepaliveSeconds))
}
}
// Get handlers
self.connectionMetadata.getLocalMaxStreamsBidirectional {
let localMaxStreams = UInt64(self.bidirectionalStreams.localMaxStreams)
self.log.debug("Local bidi max_streams=\(localMaxStreams)")
return localMaxStreams
}
self.connectionMetadata.getLocalMaxStreamsUnidirectional {
let localMaxStreams = UInt64(self.unidirectionalStreams.localMaxStreams)
self.log.debug("Local uni max_streams=\(localMaxStreams)")
return localMaxStreams
}
self.connectionMetadata.getRemoteMaxStreamsBidirectional {
let remoteMaxStreams = UInt64(self.bidirectionalStreams.remoteMaxStreams)
self.log.debug("Remote bidi max_streams=\(remoteMaxStreams)")
return remoteMaxStreams
}
self.connectionMetadata.getRemoteMaxStreamsUnidirectional {
let remoteMaxStreams = UInt64(self.unidirectionalStreams.remoteMaxStreams)
self.log.debug("Remote uni max_streams=\(remoteMaxStreams)")
return remoteMaxStreams
}
self.connectionMetadata.getKeepalive {
let keepaliveSeconds = self.keepaliveDuration.seconds
return UInt16(keepaliveSeconds)
}
self.connectionMetadata.getLocalConnectionIDs {
self.localCIDs.managedConnectionIDs.map { $0.connectionID }
}
#endif
}
func unsetMetadataHandlers() {
#if !NETWORK_EMBEDDED
// Explicitly break any strongly captured references to self from setMetadataHandlers
self.connectionMetadata.setKeepaliveHandler = nil
self.connectionMetadata.getKeepaliveHandler = nil
self.connectionMetadata.setLocalMaxStreamsBidirectionalHandler = nil
self.connectionMetadata.setLocalMaxStreamsUnidirectionalHandler = nil
self.connectionMetadata.getLocalMaxStreamsBidirectionalHandler = nil
self.connectionMetadata.getLocalMaxStreamsUnidirectionalHandler = nil
self.connectionMetadata.getRemoteMaxStreamsBidirectionalHandler = nil
self.connectionMetadata.getRemoteMaxStreamsUnidirectionalHandler = nil
self.connectionMetadata.getLocalConnectionIDsHandler = nil
#endif
}
func validateRemoteTransportParametersUpdate(
fromEarlyData old: TransportParameters,
updated new: TransportParameters
) {
guard earlyDataAccepted else { return }
// If 0-RTT data is accepted by the server, the server MUST NOT reduce any limits
// or alter any values that might be violated by the client with its 0-RTT data.
// In particular, a server that accepts 0-RTT data MUST NOT set values for the
// following parameters that are smaller than the remembered values of the parameters.
//
// - active_connection_id_limit
// - initial_max_data
// - initial_max_stream_data_bidi_local
// - initial_max_stream_data_bidi_remote
// - initial_max_stream_data_uni
// - initial_max_streams_bidi
// - initial_max_streams_uni
let oldCIDLimit = old.intValue(.activeConnectionIDLimit)
let newCIDLimit = new.intValue(.activeConnectionIDLimit)
guard newCIDLimit >= oldCIDLimit else {
log.error(
"Server reduced active_connection_id_limit from \(oldCIDLimit) to \(newCIDLimit)"
)
close(with: .protocolViolation, "Server reduced active_connection_id_limit")
return
}
let oldInitialMaxData = old.intValue(.initialMaxData)
let newInitialMaxData = new.intValue(.initialMaxData)
guard newInitialMaxData >= oldInitialMaxData else {
log.error(
"Server reduced initial_max_data from \(oldInitialMaxData) to \(newInitialMaxData)"
)
close(with: .protocolViolation, "Server reduced initial_max_data")
return
}
let oldInitialMaxStreamDataBidiLocal = old.intValue(.initialMaxStreamDataBidirectionalLocal)
let newInitialMaxStreamDataBidiLocal = new.intValue(.initialMaxStreamDataBidirectionalLocal)
guard newInitialMaxStreamDataBidiLocal >= oldInitialMaxStreamDataBidiLocal else {
log.error(
"Server reduced initial_max_stream_data_bidi_local from \(oldInitialMaxStreamDataBidiLocal) to \(newInitialMaxStreamDataBidiLocal)"
)
close(with: .protocolViolation, "Server reduced initial_max_stream_data_bidi_local")
return
}
let oldInitialMaxStreamDataBidiRemote = old.intValue(
.initialMaxStreamDataBidirectionalRemote
)
let newInitialMaxStreamDataBidiRemote = new.intValue(
.initialMaxStreamDataBidirectionalRemote
)
guard newInitialMaxStreamDataBidiRemote >= oldInitialMaxStreamDataBidiRemote else {
log.error(
"Server reduced initial_max_stream_data_bidi_remote from \(oldInitialMaxStreamDataBidiRemote) to \(newInitialMaxStreamDataBidiRemote)"
)
close(with: .protocolViolation, "Server reduced initial_max_stream_data_bidi_remote")
return
}
let oldInitialMaxStreamDataUni = old.intValue(.initialMaxStreamDataUnidirectional)
let newInitialMaxStreamDataUni = new.intValue(.initialMaxStreamDataUnidirectional)
guard newInitialMaxStreamDataUni >= oldInitialMaxStreamDataUni else {
log.error(
"Server reduced initial_max_stream_data_uni from \(oldInitialMaxStreamDataUni) to \(newInitialMaxStreamDataUni)"
)
close(with: .protocolViolation, "Server reduced initial_max_stream_data_uni")
return
}
let oldInitialMaxStreamsBidi = old.intValue(.initialMaxStreamsBidirectional)
let newInitialMaxStreamsBidi = new.intValue(.initialMaxStreamsBidirectional)
guard newInitialMaxStreamsBidi >= oldInitialMaxStreamsBidi else {
log.error(
"Server reduced initial_max_streams_bidi from \(oldInitialMaxStreamsBidi) to \(newInitialMaxStreamsBidi)"
)
close(with: .protocolViolation, "Server reduced initial_max_streams_bidi")
return
}
let oldInitialMaxStreamsUni = old.intValue(.initialMaxStreamsUnidirectional)
let newInitialMaxStreamsUni = new.intValue(.initialMaxStreamsUnidirectional)
guard newInitialMaxStreamsUni >= oldInitialMaxStreamsUni else {
log.error(
"Server reduced initial_max_streams_uni from \(oldInitialMaxStreamsUni) to \(newInitialMaxStreamsUni)"
)
close(with: .protocolViolation, "Server reduced initial_max_streams_uni")
return
}
}
// Set the remote transport parameter values, received as part of the TLS handshake
// Validation and application to the connection occurs later, when
// applyRemoteTransportParameters() is called after the handshake completes
func setRemoteTransportParameters(
_ remoteTransportParameters: TransportParameters,
earlyData: Bool
) {
if remoteTransportParametersForEarlyData, !earlyData,
let fromEarlyData = self.remoteTransportParameters
{
validateRemoteTransportParametersUpdate(
fromEarlyData: fromEarlyData,
updated: remoteTransportParameters
)
}
self.remoteTransportParametersForEarlyData = earlyData
self.remoteTransportParameters = remoteTransportParameters
}
// Application and validation of the parameters occurs here. This checks that
// the transport parameter values match the connection values (CIDs, etc),
// and updates state on the connection
func applyRemoteTransportParameters(_ remoteTransportParameters: TransportParameters) {
// An endpoint MUST treat any of the following as a connection
// error of type PROTOCOL_VIOLATION:
//
// - absence of the initial_source_connection_id transport parameter
// from either endpoint,
// - absence of the original_destination_connection_id transport
// parameter from the server,
// - absence of the retry_source_connection_id transport parameter
// from the server after receiving a Retry packet,
// - presence of the retry_source_connection_id transport parameter
// when no Retry packet was received, or
// - a mismatch between values received from a peer in these transport
// parameters and the value sent in the corresponding Destination or
// Source Connection ID fields of Initial packets.
guard let initialSCID = remoteTransportParameters[.initialSCID],
initialSCID.connectionID == self.currentPath?.dcid
else {
log.error("Missing/invalid initial SCID")
close(with: .protocolViolation, "missing/invalid initial SCID TP")
return
}
if !isServer {
guard let originalDCID = remoteTransportParameters[.originalDCID],
originalDCID.connectionID == self.originalDCID
else {
log.error("Missing/invalid original DCID")
close(with: .protocolViolation, "missing/invalid original DCID TP")
return
}
let retrySCID = remoteTransportParameters[.retrySCID]
if retryReceived {
guard let retrySCID, retrySCID.connectionID == self.retrySCID else {
log.error("Missing/invalid RETRY SCID TP")
close(with: .protocolViolation, "missing/invalid RETRY SCID TP")
return
}
} else {
guard retrySCID == nil else {
log.error("RETRY SCID TP without receiving a RETRY")
close(with: .protocolViolation, "RETRY SCID TP without receiving a RETRY")
return
}
}
}
/*
* A client MUST NOT include any server-only transport parameter:
* original_destination_connection_id, preferred_address,
* retry_source_connection_id, or stateless_reset_token.
* A server MUST treat receipt of any of these transport
* parameters as a connection error of type TRANSPORT_PARAMETER_ERROR.
*/
if self.isServer
&& (remoteTransportParameters[.originalDCID] != nil
|| remoteTransportParameters[.preferredAddress] != nil
|| remoteTransportParameters[.retrySCID] != nil
|| remoteTransportParameters[.statelessResetToken] != nil)
{
log.error("Client sent invalid transport parameters")
close(with: .transportParameterError, "invalid TP: ODCID/ISCID/SRT/PA")
return
}
// The peer's max_ack_delay is stored in the RTT struct where it's most
// often used.
if let remoteMaxAckDelay = remoteTransportParameters[.maxAckDelay] {
currentPath?.rtt.remoteMaxAckDelay = .milliseconds(remoteMaxAckDelay.value)
} else {
currentPath?.rtt.remoteMaxAckDelay = .milliseconds(
TransportParameter.defaultValue(forType: .maxAckDelay)!
)
}
if let ackDelayExponent = remoteTransportParameters[.ackDelayExponent] {
ack.remoteDelayExponent = ackDelayExponent.value
} else {
ack.remoteDelayExponent = TransportParameter.defaultValue(forType: .ackDelayExponent)!
}
// The remote transport parameter determines the limit for how many
// local CIDs we are allowed to send
if let activeConnectionIDLimit = remoteTransportParameters[.activeConnectionIDLimit] {
localCIDs.activeConnectionIDLimit = activeConnectionIDLimit.value
connectionMetadata.activeConnectionIDLimit = activeConnectionIDLimit.value
} else {
localCIDs.activeConnectionIDLimit = TransportParameter.defaultValue(
forType: .activeConnectionIDLimit
)!
connectionMetadata.activeConnectionIDLimit = TransportParameter.defaultValue(
forType: .activeConnectionIDLimit
)!
}
if let maxUDPPayloadSize = remoteTransportParameters[.maxUDPPayloadSize] {
remoteMaximumUDPPayloadSize = max(
min(maxUDPPayloadSize.value, TransportParameters.maxUDPPayloadSize),
TransportParameters.minUDPPayloadSize
)
} else {
remoteMaximumUDPPayloadSize = TransportParameter.defaultValue(
forType: .maxUDPPayloadSize
)!
}
withCurrentPath { path in
guard let dcid = path.dcid, dcid.length > 0 else {
return
}
if !isServer,
let originalStatelessResetToken = remoteTransportParameters[.statelessResetToken]
{
// Server sends its stateless reset token in transport parameters.
// Parse from transport parameters advertised from the remote.
// Therefore we delay the addition of dcid to the remote array
// till handshake completion.
do throws(QUICError) {
try remoteCIDs.insert(
sequenceNumber: 0,
connectionID: dcid,
token: originalStatelessResetToken.statelessResetToken,
used: true