@@ -17,8 +17,11 @@ import NIOCore
1717import NIOHTTP2
1818
1919struct PingHandler {
20- /// Code for ping
21- private let pingCode : UInt64
20+ /// Opaque ping data used for keep-alive pings.
21+ private let pingData : HTTP2PingData
22+
23+ /// Opaque ping data used for a ping sent after a GOAWAY frame.
24+ internal let pingDataGoAway : HTTP2PingData
2225
2326 /// The amount of time to wait before sending a keepalive ping.
2427 private let interval : TimeAmount
@@ -90,6 +93,7 @@ struct PingHandler {
9093 case schedulePing( delay: TimeAmount , timeout: TimeAmount )
9194 case cancelScheduledTimeout
9295 case reply( HTTP2Frame . FramePayload )
96+ case ratchetDownLastSeenStreamID
9397 }
9498
9599 init (
@@ -102,7 +106,8 @@ struct PingHandler {
102106 minimumReceivedPingIntervalWithoutData: TimeAmount ? = nil ,
103107 maximumPingStrikes: UInt ? = nil
104108 ) {
105- self . pingCode = pingCode
109+ self . pingData = HTTP2PingData ( withInteger: pingCode)
110+ self . pingDataGoAway = HTTP2PingData ( withInteger: ~ pingCode)
106111 self . interval = interval
107112 self . timeout = timeout
108113 self . permitWithoutCalls = permitWithoutCalls
@@ -137,8 +142,12 @@ struct PingHandler {
137142 }
138143
139144 private func handlePong( _ pingData: HTTP2PingData ) -> Action {
140- if pingData. integer == self . pingCode {
145+ if pingData == self . pingData {
141146 return . cancelScheduledTimeout
147+ } else if pingData == self . pingDataGoAway {
148+ // We received a pong for a ping we sent to trail a GOAWAY frame: this means we can now
149+ // send another GOAWAY frame with a (possibly) lower stream ID.
150+ return . ratchetDownLastSeenStreamID
142151 } else {
143152 return . none
144153 }
@@ -161,32 +170,35 @@ struct PingHandler {
161170 // This is a valid ping, reset our strike count and reply with a pong.
162171 self . pingStrikes = 0
163172 self . lastReceivedPingDate = self . now ( )
164- return . reply( self . generatePingFrame ( code : pingData. integer , ack: true ) )
173+ return . reply( self . generatePingFrame ( data : pingData, ack: true ) )
165174 }
166175 } else {
167176 // We don't support ping strikes. We'll just reply with a pong.
168177 //
169178 // Note: we don't need to update `pingStrikes` or `lastReceivedPingDate` as we don't
170179 // support ping strikes.
171- return . reply( self . generatePingFrame ( code : pingData. integer , ack: true ) )
180+ return . reply( self . generatePingFrame ( data : pingData, ack: true ) )
172181 }
173182 }
174183
175184 mutating func pingFired( ) -> Action {
176185 if self . shouldBlockPing {
177186 return . none
178187 } else {
179- return . reply( self . generatePingFrame ( code : self . pingCode , ack: false ) )
188+ return . reply( self . generatePingFrame ( data : self . pingData , ack: false ) )
180189 }
181190 }
182191
183- private mutating func generatePingFrame( code: UInt64 , ack: Bool ) -> HTTP2Frame . FramePayload {
192+ private mutating func generatePingFrame(
193+ data: HTTP2PingData ,
194+ ack: Bool
195+ ) -> HTTP2Frame . FramePayload {
184196 if self . activeStreams == 0 {
185197 self . sentPingsWithoutData += 1
186198 }
187199
188200 self . lastSentPingDate = self . now ( )
189- return HTTP2Frame . FramePayload. ping ( HTTP2PingData ( withInteger : code ) , ack: ack)
201+ return HTTP2Frame . FramePayload. ping ( data , ack: ack)
190202 }
191203
192204 /// Returns true if, on receipt of a ping, the ping should be regarded as a ping strike.
0 commit comments