View Javadoc
1   /*
2    * Copyright 2013 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.spdy;
17  
18  import io.netty.channel.ChannelDuplexHandler;
19  import io.netty.channel.ChannelFuture;
20  import io.netty.channel.ChannelFutureListener;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.channel.ChannelPromise;
23  import io.netty.util.internal.ObjectUtil;
24  
25  import java.util.concurrent.atomic.AtomicInteger;
26  
27  import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SESSION_STREAM_ID;
28  import static io.netty.handler.codec.spdy.SpdyCodecUtil.isServerId;
29  import static io.netty.util.internal.ObjectUtil.checkPositive;
30  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
31  
32  /**
33   * Manages streams within a SPDY session.
34   */
35  public class SpdySessionHandler extends ChannelDuplexHandler {
36  
37      private static final SpdyProtocolException PROTOCOL_EXCEPTION =
38              SpdyProtocolException.newStatic(null, SpdySessionHandler.class, "handleOutboundMessage(...)");
39      private static final SpdyProtocolException STREAM_CLOSED =
40              SpdyProtocolException.newStatic("Stream closed", SpdySessionHandler.class, "removeStream(...)");
41  
42      private static final int DEFAULT_WINDOW_SIZE = 64 * 1024; // 64 KB default initial window size
43      private int initialSendWindowSize    = DEFAULT_WINDOW_SIZE;
44      private int initialReceiveWindowSize = DEFAULT_WINDOW_SIZE;
45      private volatile int initialSessionReceiveWindowSize = DEFAULT_WINDOW_SIZE;
46  
47      private final SpdySession spdySession = new SpdySession(initialSendWindowSize, initialReceiveWindowSize);
48      private int lastGoodStreamId;
49  
50      // See https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2 for why 100 was chosen as a safe default.
51      private static final int DEFAULT_MAX_CONCURRENT_STREAMS = 100;
52      private int remoteConcurrentStreams = Integer.MAX_VALUE;
53      private int localConcurrentStreams  = DEFAULT_MAX_CONCURRENT_STREAMS;
54  
55      private final AtomicInteger pings = new AtomicInteger();
56  
57      private boolean sentGoAwayFrame;
58      private boolean receivedGoAwayFrame;
59      private boolean sentInitialSettingsFrame;
60  
61      private ChannelFutureListener closeSessionFutureListener;
62  
63      private final boolean server;
64      private final int minorVersion;
65  
66      /**
67       * Creates a new session handler.
68       * <p>
69       * Remote-initiated streams are accepted up to {@value #DEFAULT_MAX_CONCURRENT_STREAMS}
70       * concurrently (matching the HTTP/2 default, see
71       * <a href="https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2">RFC 7540, Section 6.5.2</a>).
72       * Use {@link #SpdySessionHandler(SpdyVersion, boolean, int)} to configure a different limit.
73       *
74       * @param version the protocol version
75       * @param server  {@code true} if and only if this session handler should
76       *                handle the server endpoint of the connection.
77       *                {@code false} if and only if this session handler should
78       *                handle the client endpoint of the connection.
79       */
80      public SpdySessionHandler(SpdyVersion version, boolean server) {
81          this(version, server, DEFAULT_MAX_CONCURRENT_STREAMS);
82      }
83  
84      /**
85       * Creates a new session handler.
86       *
87       * @param version the protocol version
88       * @param server  {@code true} if and only if this session handler should
89       *                handle the server endpoint of the connection.
90       *                {@code false} if and only if this session handler should
91       *                handle the client endpoint of the connection.
92       * @param maxLocalConcurrentStreams the maximum number of concurrent remote-initiated streams
93       *                that will be accepted. This bounds the memory a remote peer can force this
94       *                handler to allocate by opening streams. {@code SYN_STREAM} frames received
95       *                once this limit is reached are rejected with a {@code RST_STREAM} carrying
96       *                {@link SpdyStreamStatus#REFUSED_STREAM}. Must be positive.
97       */
98      public SpdySessionHandler(SpdyVersion version, boolean server, int maxLocalConcurrentStreams) {
99          this.minorVersion = ObjectUtil.checkNotNull(version, "version").minorVersion();
100         this.server = server;
101         this.localConcurrentStreams = checkPositive(maxLocalConcurrentStreams, "maxLocalConcurrentStreams");
102     }
103 
104     public void setSessionReceiveWindowSize(int sessionReceiveWindowSize) {
105         checkPositiveOrZero(sessionReceiveWindowSize, "sessionReceiveWindowSize");
106         // This will not send a window update frame immediately.
107         // If this value increases the allowed receive window size,
108         // a WINDOW_UPDATE frame will be sent when only half of the
109         // session window size remains during data frame processing.
110         // If this value decreases the allowed receive window size,
111         // the window will be reduced as data frames are processed.
112         initialSessionReceiveWindowSize = sessionReceiveWindowSize;
113     }
114 
115     @Override
116     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
117         if (ctx.channel().isActive()) {
118             sendInitialSettingsFrame(ctx);
119         }
120     }
121 
122     @Override
123     public void channelActive(ChannelHandlerContext ctx) throws Exception {
124         sendInitialSettingsFrame(ctx);
125         super.channelActive(ctx);
126     }
127 
128     private void sendInitialSettingsFrame(ChannelHandlerContext ctx) {
129         if (sentInitialSettingsFrame) {
130             return;
131         }
132         sentInitialSettingsFrame = true;
133         // Advertise the accept limit to the remote peer. This is advisory only -- the limit is
134         // enforced locally by acceptStream(...) regardless of whether the peer honors it.
135         SpdySettingsFrame settingsFrame = new DefaultSpdySettingsFrame();
136         settingsFrame.setValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS, localConcurrentStreams);
137         ctx.writeAndFlush(settingsFrame);
138     }
139 
140     @Override
141     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
142         if (msg instanceof SpdyDataFrame) {
143 
144             /*
145              * SPDY Data frame processing requirements:
146              *
147              * If an endpoint receives a data frame for a Stream-ID which is not open
148              * and the endpoint has not sent a GOAWAY frame, it must issue a stream error
149              * with the error code INVALID_STREAM for the Stream-ID.
150              *
151              * If an endpoint which created the stream receives a data frame before receiving
152              * a SYN_REPLY on that stream, it is a protocol error, and the recipient must
153              * issue a stream error with the getStatus code PROTOCOL_ERROR for the Stream-ID.
154              *
155              * If an endpoint receives multiple data frames for invalid Stream-IDs,
156              * it may close the session.
157              *
158              * If an endpoint refuses a stream it must ignore any data frames for that stream.
159              *
160              * If an endpoint receives a data frame after the stream is half-closed from the
161              * sender, it must send a RST_STREAM frame with the getStatus STREAM_ALREADY_CLOSED.
162              *
163              * If an endpoint receives a data frame after the stream is closed, it must send
164              * a RST_STREAM frame with the getStatus PROTOCOL_ERROR.
165              */
166             SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
167             int streamId = spdyDataFrame.streamId();
168 
169             int deltaWindowSize = -1 * spdyDataFrame.content().readableBytes();
170             int newSessionWindowSize =
171                 spdySession.updateReceiveWindowSize(SPDY_SESSION_STREAM_ID, deltaWindowSize);
172 
173             // Check if session window size is reduced beyond allowable lower bound
174             if (newSessionWindowSize < 0) {
175                 spdyDataFrame.release();
176                 issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
177                 return;
178             }
179 
180             // Send a WINDOW_UPDATE frame if less than half the session window size remains
181             if (newSessionWindowSize <= initialSessionReceiveWindowSize / 2) {
182                 int sessionDeltaWindowSize = initialSessionReceiveWindowSize - newSessionWindowSize;
183                 spdySession.updateReceiveWindowSize(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
184                 SpdyWindowUpdateFrame spdyWindowUpdateFrame =
185                     new DefaultSpdyWindowUpdateFrame(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
186                 ctx.writeAndFlush(spdyWindowUpdateFrame);
187             }
188 
189             // Check if we received a data frame for a Stream-ID which is not open
190 
191             if (!spdySession.isActiveStream(streamId)) {
192                 spdyDataFrame.release();
193                 if (streamId <= lastGoodStreamId) {
194                     issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
195                 } else if (!sentGoAwayFrame) {
196                     issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
197                 }
198                 return;
199             }
200 
201             // Check if we received a data frame for a stream which is half-closed
202 
203             if (spdySession.isRemoteSideClosed(streamId)) {
204                 spdyDataFrame.release();
205                 issueStreamError(ctx, streamId, SpdyStreamStatus.STREAM_ALREADY_CLOSED);
206                 return;
207             }
208 
209             // Check if we received a data frame before receiving a SYN_REPLY
210             if (!isRemoteInitiatedId(streamId) && !spdySession.hasReceivedReply(streamId)) {
211                 spdyDataFrame.release();
212                 issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
213                 return;
214             }
215 
216             /*
217              * SPDY Data frame flow control processing requirements:
218              *
219              * Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.
220              */
221 
222             // Update receive window size
223             int newWindowSize = spdySession.updateReceiveWindowSize(streamId, deltaWindowSize);
224 
225             // Window size can become negative if we sent a SETTINGS frame that reduces the
226             // size of the transfer window after the peer has written data frames.
227             // The value is bounded by the length that SETTINGS frame decrease the window.
228             // This difference is stored for the session when writing the SETTINGS frame
229             // and is cleared once we send a WINDOW_UPDATE frame.
230             if (newWindowSize < spdySession.getReceiveWindowSizeLowerBound(streamId)) {
231                 spdyDataFrame.release();
232                 issueStreamError(ctx, streamId, SpdyStreamStatus.FLOW_CONTROL_ERROR);
233                 return;
234             }
235 
236             // Window size became negative due to sender writing frame before receiving SETTINGS
237             // Send data frames upstream in initialReceiveWindowSize chunks
238             if (newWindowSize < 0) {
239                 while (spdyDataFrame.content().readableBytes() > initialReceiveWindowSize) {
240                     SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
241                             streamId, spdyDataFrame.content().readRetainedSlice(initialReceiveWindowSize));
242                     ctx.writeAndFlush(partialDataFrame);
243                 }
244             }
245 
246             // Send a WINDOW_UPDATE frame if less than half the stream window size remains
247             if (newWindowSize <= initialReceiveWindowSize / 2 && !spdyDataFrame.isLast()) {
248                 int streamDeltaWindowSize = initialReceiveWindowSize - newWindowSize;
249                 spdySession.updateReceiveWindowSize(streamId, streamDeltaWindowSize);
250                 SpdyWindowUpdateFrame spdyWindowUpdateFrame =
251                         new DefaultSpdyWindowUpdateFrame(streamId, streamDeltaWindowSize);
252                 ctx.writeAndFlush(spdyWindowUpdateFrame);
253             }
254 
255             // Close the remote side of the stream if this is the last frame
256             if (spdyDataFrame.isLast()) {
257                 halfCloseStream(streamId, true, ctx.newSucceededFuture());
258             }
259 
260         } else if (msg instanceof SpdySynStreamFrame) {
261 
262             /*
263              * SPDY SYN_STREAM frame processing requirements:
264              *
265              * If an endpoint receives a SYN_STREAM with a Stream-ID that is less than
266              * any previously received SYN_STREAM, it must issue a session error with
267              * the getStatus PROTOCOL_ERROR.
268              *
269              * If an endpoint receives multiple SYN_STREAM frames with the same active
270              * Stream-ID, it must issue a stream error with the getStatus code PROTOCOL_ERROR.
271              *
272              * The recipient can reject a stream by sending a stream error with the
273              * getStatus code REFUSED_STREAM.
274              */
275 
276             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
277             int streamId = spdySynStreamFrame.streamId();
278 
279             // Check if we received a valid SYN_STREAM frame
280             if (spdySynStreamFrame.isInvalid() ||
281                 !isRemoteInitiatedId(streamId) ||
282                 spdySession.isActiveStream(streamId)) {
283                 issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
284                 return;
285             }
286 
287             // Stream-IDs must be monotonically increasing
288             if (streamId <= lastGoodStreamId) {
289                 issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
290                 return;
291             }
292 
293             // Try to accept the stream
294             byte priority = spdySynStreamFrame.priority();
295             boolean remoteSideClosed = spdySynStreamFrame.isLast();
296             boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
297             if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) {
298                 issueStreamError(ctx, streamId, SpdyStreamStatus.REFUSED_STREAM);
299                 return;
300             }
301 
302         } else if (msg instanceof SpdySynReplyFrame) {
303 
304             /*
305              * SPDY SYN_REPLY frame processing requirements:
306              *
307              * If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID
308              * it must issue a stream error with the getStatus code STREAM_IN_USE.
309              */
310 
311             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
312             int streamId = spdySynReplyFrame.streamId();
313 
314             // Check if we received a valid SYN_REPLY frame
315             if (spdySynReplyFrame.isInvalid() ||
316                 isRemoteInitiatedId(streamId) ||
317                 spdySession.isRemoteSideClosed(streamId)) {
318                 issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
319                 return;
320             }
321 
322             // Check if we have received multiple frames for the same Stream-ID
323             if (spdySession.hasReceivedReply(streamId)) {
324                 issueStreamError(ctx, streamId, SpdyStreamStatus.STREAM_IN_USE);
325                 return;
326             }
327 
328             spdySession.receivedReply(streamId);
329 
330             // Close the remote side of the stream if this is the last frame
331             if (spdySynReplyFrame.isLast()) {
332                 halfCloseStream(streamId, true, ctx.newSucceededFuture());
333             }
334 
335         } else if (msg instanceof SpdyRstStreamFrame) {
336 
337             /*
338              * SPDY RST_STREAM frame processing requirements:
339              *
340              * After receiving a RST_STREAM on a stream, the receiver must not send
341              * additional frames on that stream.
342              *
343              * An endpoint must not send a RST_STREAM in response to a RST_STREAM.
344              */
345 
346             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
347             removeStream(spdyRstStreamFrame.streamId(), ctx.newSucceededFuture());
348 
349         } else if (msg instanceof SpdySettingsFrame) {
350 
351             SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
352 
353             int settingsMinorVersion = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION);
354             if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) {
355                 // Settings frame had the wrong minor version
356                 issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
357                 return;
358             }
359 
360             int newConcurrentStreams =
361                 spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
362             if (newConcurrentStreams >= 0) {
363                 remoteConcurrentStreams = newConcurrentStreams;
364             }
365 
366             // Persistence flag are inconsistent with the use of SETTINGS to communicate
367             // the initial window size. Remove flags from the sender requesting that the
368             // value be persisted. Remove values that the sender indicates are persisted.
369             if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
370                 spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
371             }
372             spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
373 
374             int newInitialWindowSize =
375                 spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
376             if (newInitialWindowSize >= 0) {
377                 updateInitialSendWindowSize(newInitialWindowSize);
378             }
379 
380         } else if (msg instanceof SpdyPingFrame) {
381 
382             /*
383              * SPDY PING frame processing requirements:
384              *
385              * Receivers of a PING frame should send an identical frame to the sender
386              * as soon as possible.
387              *
388              * Receivers of a PING frame must ignore frames that it did not initiate
389              */
390 
391             SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
392 
393             if (isRemoteInitiatedId(spdyPingFrame.id())) {
394                 ctx.writeAndFlush(spdyPingFrame);
395                 return;
396             }
397 
398             // Note: only checks that there are outstanding pings since uniqueness is not enforced
399             if (pings.get() == 0) {
400                 return;
401             }
402             pings.getAndDecrement();
403 
404         } else if (msg instanceof SpdyGoAwayFrame) {
405 
406             receivedGoAwayFrame = true;
407 
408         } else if (msg instanceof SpdyHeadersFrame) {
409 
410             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
411             int streamId = spdyHeadersFrame.streamId();
412 
413             // Check if we received a valid HEADERS frame
414             if (spdyHeadersFrame.isInvalid()) {
415                 issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
416                 return;
417             }
418 
419             if (spdySession.isRemoteSideClosed(streamId)) {
420                 issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
421                 return;
422             }
423 
424             // Close the remote side of the stream if this is the last frame
425             if (spdyHeadersFrame.isLast()) {
426                 halfCloseStream(streamId, true, ctx.newSucceededFuture());
427             }
428 
429         } else if (msg instanceof SpdyWindowUpdateFrame) {
430 
431             /*
432              * SPDY WINDOW_UPDATE frame processing requirements:
433              *
434              * Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31
435              * must send a RST_STREAM with the getStatus code FLOW_CONTROL_ERROR.
436              *
437              * Sender should ignore all WINDOW_UPDATE frames associated with a stream
438              * after sending the last frame for the stream.
439              */
440 
441             SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
442             int streamId = spdyWindowUpdateFrame.streamId();
443             int deltaWindowSize = spdyWindowUpdateFrame.deltaWindowSize();
444 
445             // Ignore frames for half-closed streams
446             if (streamId != SPDY_SESSION_STREAM_ID && spdySession.isLocalSideClosed(streamId)) {
447                 return;
448             }
449 
450             // Check for numerical overflow
451             if (spdySession.getSendWindowSize(streamId) > Integer.MAX_VALUE - deltaWindowSize) {
452                 if (streamId == SPDY_SESSION_STREAM_ID) {
453                     issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
454                 } else {
455                     issueStreamError(ctx, streamId, SpdyStreamStatus.FLOW_CONTROL_ERROR);
456                 }
457                 return;
458             }
459 
460             updateSendWindowSize(ctx, streamId, deltaWindowSize);
461         }
462 
463         ctx.fireChannelRead(msg);
464     }
465 
466     @Override
467     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
468         for (Integer streamId: spdySession.activeStreams().keySet()) {
469             removeStream(streamId, ctx.newSucceededFuture());
470         }
471         ctx.fireChannelInactive();
472     }
473 
474     @Override
475     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
476         if (cause instanceof SpdyProtocolException) {
477             issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
478         }
479 
480         ctx.fireExceptionCaught(cause);
481     }
482 
483     @Override
484     public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
485         sendGoAwayFrame(ctx, promise);
486     }
487 
488     @Override
489     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
490         if (msg instanceof SpdyDataFrame ||
491             msg instanceof SpdySynStreamFrame ||
492             msg instanceof SpdySynReplyFrame ||
493             msg instanceof SpdyRstStreamFrame ||
494             msg instanceof SpdySettingsFrame ||
495             msg instanceof SpdyPingFrame ||
496             msg instanceof SpdyGoAwayFrame ||
497             msg instanceof SpdyHeadersFrame ||
498             msg instanceof SpdyWindowUpdateFrame) {
499 
500             handleOutboundMessage(ctx, msg, promise);
501         } else {
502             ctx.write(msg, promise);
503         }
504     }
505 
506     private void handleOutboundMessage(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
507         if (msg instanceof SpdyDataFrame) {
508 
509             SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
510             int streamId = spdyDataFrame.streamId();
511 
512             // Frames must not be sent on half-closed streams
513             if (spdySession.isLocalSideClosed(streamId)) {
514                 spdyDataFrame.release();
515                 promise.setFailure(PROTOCOL_EXCEPTION);
516                 return;
517             }
518 
519             /*
520              * SPDY Data frame flow control processing requirements:
521              *
522              * Sender must not send a data frame with data length greater
523              * than the transfer window size.
524              *
525              * After sending each data frame, the sender decrements its
526              * transfer window size by the amount of data transmitted.
527              *
528              * When the window size becomes less than or equal to 0, the
529              * sender must pause transmitting data frames.
530              */
531 
532             int dataLength = spdyDataFrame.content().readableBytes();
533             int sendWindowSize = spdySession.getSendWindowSize(streamId);
534             int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID);
535             sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize);
536 
537             if (sendWindowSize <= 0) {
538                 // Stream is stalled -- enqueue Data frame and return
539                 spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise));
540                 return;
541             } else if (sendWindowSize < dataLength) {
542                 // Stream is not stalled but we cannot send the entire frame
543                 spdySession.updateSendWindowSize(streamId, -1 * sendWindowSize);
544                 spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize);
545 
546                 // Create a partial data frame whose length is the current window size
547                 SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
548                         streamId, spdyDataFrame.content().readRetainedSlice(sendWindowSize));
549 
550                 // Enqueue the remaining data (will be the first frame queued)
551                 spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise));
552 
553                 // The transfer window size is pre-decremented when sending a data frame downstream.
554                 // Close the session on write failures that leave the transfer window in a corrupt state.
555                 final ChannelHandlerContext context = ctx;
556                 ctx.write(partialDataFrame).addListener(future -> {
557                     if (!future.isSuccess()) {
558                         issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR);
559                     }
560                 });
561                 return;
562             } else {
563                 // Window size is large enough to send entire data frame
564                 spdySession.updateSendWindowSize(streamId, -1 * dataLength);
565                 spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataLength);
566 
567                 // The transfer window size is pre-decremented when sending a data frame downstream.
568                 // Close the session on write failures that leave the transfer window in a corrupt state.
569                 final ChannelHandlerContext context = ctx;
570                 promise.addListener(future -> {
571                     if (!future.isSuccess()) {
572                         issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR);
573                     }
574                 });
575             }
576 
577             // Close the local side of the stream if this is the last frame
578             if (spdyDataFrame.isLast()) {
579                 halfCloseStream(streamId, false, promise);
580             }
581 
582         } else if (msg instanceof SpdySynStreamFrame) {
583 
584             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
585             int streamId = spdySynStreamFrame.streamId();
586 
587             if (isRemoteInitiatedId(streamId)) {
588                 promise.setFailure(PROTOCOL_EXCEPTION);
589                 return;
590             }
591 
592             byte priority = spdySynStreamFrame.priority();
593             boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional();
594             boolean localSideClosed = spdySynStreamFrame.isLast();
595             if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) {
596                 promise.setFailure(PROTOCOL_EXCEPTION);
597                 return;
598             }
599 
600         } else if (msg instanceof SpdySynReplyFrame) {
601 
602             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
603             int streamId = spdySynReplyFrame.streamId();
604 
605             // Frames must not be sent on half-closed streams
606             if (!isRemoteInitiatedId(streamId) || spdySession.isLocalSideClosed(streamId)) {
607                 promise.setFailure(PROTOCOL_EXCEPTION);
608                 return;
609             }
610 
611             // Close the local side of the stream if this is the last frame
612             if (spdySynReplyFrame.isLast()) {
613                 halfCloseStream(streamId, false, promise);
614             }
615 
616         } else if (msg instanceof SpdyRstStreamFrame) {
617 
618             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
619             removeStream(spdyRstStreamFrame.streamId(), promise);
620 
621         } else if (msg instanceof SpdySettingsFrame) {
622 
623             SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
624 
625             int settingsMinorVersion = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION);
626             if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) {
627                 // Settings frame had the wrong minor version
628                 promise.setFailure(PROTOCOL_EXCEPTION);
629                 return;
630             }
631 
632             int newConcurrentStreams =
633                     spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
634             if (newConcurrentStreams >= 0) {
635                 localConcurrentStreams = newConcurrentStreams;
636             }
637 
638             // Persistence flag are inconsistent with the use of SETTINGS to communicate
639             // the initial window size. Remove flags from the sender requesting that the
640             // value be persisted. Remove values that the sender indicates are persisted.
641             if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
642                 spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
643             }
644             spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
645 
646             int newInitialWindowSize =
647                     spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
648             if (newInitialWindowSize >= 0) {
649                 updateInitialReceiveWindowSize(newInitialWindowSize);
650             }
651 
652         } else if (msg instanceof SpdyPingFrame) {
653 
654             SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
655             if (isRemoteInitiatedId(spdyPingFrame.id())) {
656                 ctx.fireExceptionCaught(new IllegalArgumentException(
657                             "invalid PING ID: " + spdyPingFrame.id()));
658                 return;
659             }
660             pings.getAndIncrement();
661 
662         } else if (msg instanceof SpdyGoAwayFrame) {
663 
664             // Why is this being sent? Intercept it and fail the write.
665             // Should have sent a CLOSE ChannelStateEvent
666             promise.setFailure(PROTOCOL_EXCEPTION);
667             return;
668 
669         } else if (msg instanceof SpdyHeadersFrame) {
670 
671             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
672             int streamId = spdyHeadersFrame.streamId();
673 
674             // Frames must not be sent on half-closed streams
675             if (spdySession.isLocalSideClosed(streamId)) {
676                 promise.setFailure(PROTOCOL_EXCEPTION);
677                 return;
678             }
679 
680             // Close the local side of the stream if this is the last frame
681             if (spdyHeadersFrame.isLast()) {
682                 halfCloseStream(streamId, false, promise);
683             }
684 
685         } else if (msg instanceof SpdyWindowUpdateFrame) {
686 
687             // Why is this being sent? Intercept it and fail the write.
688             promise.setFailure(PROTOCOL_EXCEPTION);
689             return;
690         }
691 
692         ctx.write(msg, promise);
693     }
694 
695     /*
696      * SPDY Session Error Handling:
697      *
698      * When a session error occurs, the endpoint encountering the error must first
699      * send a GOAWAY frame with the Stream-ID of the most recently received stream
700      * from the remote endpoint, and the error code for why the session is terminating.
701      *
702      * After sending the GOAWAY frame, the endpoint must close the TCP connection.
703      */
704     private void issueSessionError(
705             ChannelHandlerContext ctx, SpdySessionStatus status) {
706 
707         sendGoAwayFrame(ctx, status).addListener(new ClosingChannelFutureListener(ctx, ctx.newPromise()));
708     }
709 
710     /*
711      * SPDY Stream Error Handling:
712      *
713      * Upon a stream error, the endpoint must send a RST_STREAM frame which contains
714      * the Stream-ID for the stream where the error occurred and the error getStatus which
715      * caused the error.
716      *
717      * After sending the RST_STREAM, the stream is closed to the sending endpoint.
718      *
719      * Note: this is only called by the worker thread
720      */
721     private void issueStreamError(ChannelHandlerContext ctx, int streamId, SpdyStreamStatus status) {
722         boolean fireChannelRead = !spdySession.isRemoteSideClosed(streamId);
723         ChannelPromise promise = ctx.newPromise();
724         removeStream(streamId, promise);
725 
726         SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, status);
727         ctx.writeAndFlush(spdyRstStreamFrame, promise);
728         if (fireChannelRead) {
729             ctx.fireChannelRead(spdyRstStreamFrame);
730         }
731     }
732 
733     /*
734      * Helper functions
735      */
736 
737     private boolean isRemoteInitiatedId(int id) {
738         boolean serverId = isServerId(id);
739         return server && !serverId || !server && serverId;
740     }
741 
742     // need to synchronize to prevent new streams from being created while updating active streams
743     private void updateInitialSendWindowSize(int newInitialWindowSize) {
744         int deltaWindowSize = newInitialWindowSize - initialSendWindowSize;
745         initialSendWindowSize = newInitialWindowSize;
746         spdySession.updateAllSendWindowSizes(deltaWindowSize);
747     }
748 
749     // need to synchronize to prevent new streams from being created while updating active streams
750     private void updateInitialReceiveWindowSize(int newInitialWindowSize) {
751         int deltaWindowSize = newInitialWindowSize - initialReceiveWindowSize;
752         initialReceiveWindowSize = newInitialWindowSize;
753         spdySession.updateAllReceiveWindowSizes(deltaWindowSize);
754     }
755 
756     // need to synchronize accesses to sentGoAwayFrame, lastGoodStreamId, and initial window sizes
757     private boolean acceptStream(
758             int streamId, byte priority, boolean remoteSideClosed, boolean localSideClosed) {
759         // Cannot initiate any new streams after receiving or sending GOAWAY
760         if (receivedGoAwayFrame || sentGoAwayFrame) {
761             return false;
762         }
763 
764         boolean remote = isRemoteInitiatedId(streamId);
765         int maxConcurrentStreams = remote ? localConcurrentStreams : remoteConcurrentStreams;
766         if (spdySession.numActiveStreams(remote) >= maxConcurrentStreams) {
767             return false;
768         }
769         spdySession.acceptStream(
770                 streamId, priority, remoteSideClosed, localSideClosed,
771                 initialSendWindowSize, initialReceiveWindowSize, remote);
772         if (remote) {
773             lastGoodStreamId = streamId;
774         }
775         return true;
776     }
777 
778     private void halfCloseStream(int streamId, boolean remote, ChannelFuture future) {
779         if (remote) {
780             spdySession.closeRemoteSide(streamId, isRemoteInitiatedId(streamId));
781         } else {
782             spdySession.closeLocalSide(streamId, isRemoteInitiatedId(streamId));
783         }
784         if (closeSessionFutureListener != null && spdySession.noActiveStreams()) {
785             future.addListener(closeSessionFutureListener);
786         }
787     }
788 
789     private void removeStream(int streamId, ChannelFuture future) {
790         spdySession.removeStream(streamId, STREAM_CLOSED, isRemoteInitiatedId(streamId));
791 
792         if (closeSessionFutureListener != null && spdySession.noActiveStreams()) {
793             future.addListener(closeSessionFutureListener);
794         }
795     }
796 
797     private void updateSendWindowSize(final ChannelHandlerContext ctx, int streamId, int deltaWindowSize) {
798         spdySession.updateSendWindowSize(streamId, deltaWindowSize);
799 
800         while (true) {
801             // Check if we have unblocked a stalled stream
802             SpdySession.PendingWrite pendingWrite = spdySession.getPendingWrite(streamId);
803             if (pendingWrite == null) {
804                 return;
805             }
806 
807             SpdyDataFrame spdyDataFrame = pendingWrite.spdyDataFrame;
808             int dataFrameSize = spdyDataFrame.content().readableBytes();
809             int writeStreamId = spdyDataFrame.streamId();
810             int sendWindowSize = spdySession.getSendWindowSize(writeStreamId);
811             int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID);
812             sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize);
813 
814             if (sendWindowSize <= 0) {
815                 return;
816             } else if (sendWindowSize < dataFrameSize) {
817                 // We can send a partial frame
818                 spdySession.updateSendWindowSize(writeStreamId, -1 * sendWindowSize);
819                 spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize);
820 
821                 // Create a partial data frame whose length is the current window size
822                 SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
823                         writeStreamId, spdyDataFrame.content().readRetainedSlice(sendWindowSize));
824 
825                 // The transfer window size is pre-decremented when sending a data frame downstream.
826                 ctx.writeAndFlush(partialDataFrame).addListener(future -> {
827                     if (!future.isSuccess()) {
828                         issueSessionError(ctx, SpdySessionStatus.INTERNAL_ERROR);
829                     }
830                 });
831             } else {
832                 // Window size is large enough to send entire data frame
833                 spdySession.removePendingWrite(writeStreamId);
834                 spdySession.updateSendWindowSize(writeStreamId, -1 * dataFrameSize);
835                 spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataFrameSize);
836 
837                 // Close the local side of the stream if this is the last frame
838                 if (spdyDataFrame.isLast()) {
839                     halfCloseStream(writeStreamId, false, pendingWrite.promise);
840                 }
841 
842                 // The transfer window size is pre-decremented when sending a data frame downstream.
843                 // Close the session on write failures that leave the transfer window in a corrupt state.
844                 ctx.writeAndFlush(spdyDataFrame, pendingWrite.promise).addListener(future -> {
845                     if (!future.isSuccess()) {
846                         issueSessionError(ctx, SpdySessionStatus.INTERNAL_ERROR);
847                     }
848                 });
849             }
850         }
851     }
852 
853     private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelPromise future) {
854         // Avoid NotYetConnectedException
855         if (!ctx.channel().isActive()) {
856             ctx.close(future);
857             return;
858         }
859 
860         ChannelFuture f = sendGoAwayFrame(ctx, SpdySessionStatus.OK);
861         if (spdySession.noActiveStreams()) {
862             f.addListener(new ClosingChannelFutureListener(ctx, future));
863         } else {
864             closeSessionFutureListener = new ClosingChannelFutureListener(ctx, future);
865         }
866         // FIXME: Close the connection forcibly after timeout.
867     }
868 
869     private ChannelFuture sendGoAwayFrame(
870             ChannelHandlerContext ctx, SpdySessionStatus status) {
871         if (!sentGoAwayFrame) {
872             sentGoAwayFrame = true;
873             SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamId, status);
874             return ctx.writeAndFlush(spdyGoAwayFrame);
875         } else {
876             return ctx.newSucceededFuture();
877         }
878     }
879 
880     private static final class ClosingChannelFutureListener implements ChannelFutureListener {
881         private final ChannelHandlerContext ctx;
882         private final ChannelPromise promise;
883 
884         ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelPromise promise) {
885             this.ctx = ctx;
886             this.promise = promise;
887         }
888 
889         @Override
890         public void operationComplete(ChannelFuture sentGoAwayFuture) throws Exception {
891             ctx.close(promise);
892         }
893     }
894 }