@@ -9,42 +9,87 @@ package quic
9
9
import (
10
10
"errors"
11
11
"fmt"
12
+ "net/netip"
12
13
"time"
13
14
)
14
15
15
16
// A Conn is a QUIC connection.
16
17
//
17
18
// Multiple goroutines may invoke methods on a Conn simultaneously.
18
19
type Conn struct {
20
+ side connSide
21
+ listener connListener
22
+ testHooks connTestHooks
23
+ peerAddr netip.AddrPort
24
+
19
25
msgc chan any
20
26
donec chan struct {} // closed when conn loop exits
21
27
exited bool // set to make the conn loop exit immediately
22
28
23
- testHooks connTestHooks
29
+ w packetWriter
30
+ acks [numberSpaceCount ]ackState // indexed by number space
31
+ connIDState connIDState
32
+ tlsState tlsState
33
+ loss lossState
24
34
25
35
// idleTimeout is the time at which the connection will be closed due to inactivity.
26
36
// https://www.rfc-editor.org/rfc/rfc9000#section-10.1
27
37
maxIdleTimeout time.Duration
28
38
idleTimeout time.Time
39
+
40
+ peerAckDelayExponent int8 // -1 when unknown
41
+
42
+ // Tests only: Send a PING in a specific number space.
43
+ testSendPingSpace numberSpace
44
+ testSendPing sentVal
45
+ }
46
+
47
+ // The connListener is the Conn's Listener.
48
+ // Defined as an interface so we can swap it out in tests.
49
+ type connListener interface {
50
+ sendDatagram (p []byte , addr netip.AddrPort ) error
29
51
}
30
52
31
53
// connTestHooks override conn behavior in tests.
32
54
type connTestHooks interface {
33
55
nextMessage (msgc chan any , nextTimeout time.Time ) (now time.Time , message any )
34
56
}
35
57
36
- func newConn (now time.Time , hooks connTestHooks ) (* Conn , error ) {
58
+ func newConn (now time.Time , side connSide , initialConnID [] byte , peerAddr netip. AddrPort , l connListener , hooks connTestHooks ) (* Conn , error ) {
37
59
c := & Conn {
38
- donec : make (chan struct {}),
39
- testHooks : hooks ,
40
- maxIdleTimeout : defaultMaxIdleTimeout ,
41
- idleTimeout : now .Add (defaultMaxIdleTimeout ),
60
+ side : side ,
61
+ listener : l ,
62
+ peerAddr : peerAddr ,
63
+ msgc : make (chan any , 1 ),
64
+ donec : make (chan struct {}),
65
+ testHooks : hooks ,
66
+ maxIdleTimeout : defaultMaxIdleTimeout ,
67
+ idleTimeout : now .Add (defaultMaxIdleTimeout ),
68
+ peerAckDelayExponent : - 1 ,
42
69
}
43
70
44
71
// A one-element buffer allows us to wake a Conn's event loop as a
45
72
// non-blocking operation.
46
73
c .msgc = make (chan any , 1 )
47
74
75
+ if c .side == clientSide {
76
+ if err := c .connIDState .initClient (newRandomConnID ); err != nil {
77
+ return nil , err
78
+ }
79
+ initialConnID = c .connIDState .dstConnID ()
80
+ } else {
81
+ if err := c .connIDState .initServer (newRandomConnID , initialConnID ); err != nil {
82
+ return nil , err
83
+ }
84
+ }
85
+
86
+ // The smallest allowed maximum QUIC datagram size is 1200 bytes.
87
+ // TODO: PMTU discovery.
88
+ const maxDatagramSize = 1200
89
+ c .loss .init (c .side , maxDatagramSize , now )
90
+
91
+ c .tlsState .init (c .side , initialConnID )
92
+
48
93
go c .loop (now )
49
94
return c , nil
50
95
}
@@ -76,7 +121,14 @@ func (c *Conn) loop(now time.Time) {
76
121
}
77
122
78
123
for ! c .exited {
79
- nextTimeout := c .idleTimeout
124
+ sendTimeout := c .maybeSend (now ) // try sending
125
+
126
+ // Note that we only need to consider the ack timer for the App Data space,
127
+ // since the Initial and Handshake spaces always ack immediately.
128
+ nextTimeout := sendTimeout
129
+ nextTimeout = firstTime (nextTimeout , c .idleTimeout )
130
+ nextTimeout = firstTime (nextTimeout , c .loss .timer )
131
+ nextTimeout = firstTime (nextTimeout , c .acks [appDataSpace ].nextAck )
80
132
81
133
var m any
82
134
if hooks != nil {
@@ -100,6 +152,9 @@ func (c *Conn) loop(now time.Time) {
100
152
now = time .Now ()
101
153
}
102
154
switch m := m .(type ) {
155
+ case * datagram :
156
+ c .handleDatagram (now , m )
157
+ m .recycle ()
103
158
case timerEvent :
104
159
// A connection timer has expired.
105
160
if ! now .Before (c .idleTimeout ) {
@@ -109,6 +164,7 @@ func (c *Conn) loop(now time.Time) {
109
164
c .exited = true
110
165
return
111
166
}
167
+ c .loss .advance (now , c .handleAckOrLoss )
112
168
case func (time.Time , * Conn ):
113
169
// Send a func to msgc to run it on the main Conn goroutine
114
170
m (now , c )
@@ -146,10 +202,30 @@ func (c *Conn) runOnLoop(f func(now time.Time, c *Conn)) error {
146
202
return nil
147
203
}
148
204
205
+ // abort terminates a connection with an error.
206
+ func (c * Conn ) abort (now time.Time , err error ) {
207
+ // TODO: Send CONNECTION_CLOSE frames.
208
+ c .exit ()
209
+ }
210
+
149
211
// exit fully terminates a connection immediately.
150
212
func (c * Conn ) exit () {
151
213
c .runOnLoop (func (now time.Time , c * Conn ) {
152
214
c .exited = true
153
215
})
154
216
<- c .donec
155
217
}
218
+
219
+ // firstTime returns the earliest non-zero time, or zero if both times are zero.
220
+ func firstTime (a , b time.Time ) time.Time {
221
+ switch {
222
+ case a .IsZero ():
223
+ return b
224
+ case b .IsZero ():
225
+ return a
226
+ case a .Before (b ):
227
+ return a
228
+ default :
229
+ return b
230
+ }
231
+ }
0 commit comments