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                 issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
176                 return;
177             }
178 
179             // Send a WINDOW_UPDATE frame if less than half the session window size remains
180             if (newSessionWindowSize <= initialSessionReceiveWindowSize / 2) {
181                 int sessionDeltaWindowSize = initialSessionReceiveWindowSize - newSessionWindowSize;
182                 spdySession.updateReceiveWindowSize(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
183                 SpdyWindowUpdateFrame spdyWindowUpdateFrame =
184                     new DefaultSpdyWindowUpdateFrame(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
185                 ctx.writeAndFlush(spdyWindowUpdateFrame);
186             }
187 
188             // Check if we received a data frame for a Stream-ID which is not open
189 
190             if (!spdySession.isActiveStream(streamId)) {
191                 spdyDataFrame.release();
192                 if (streamId <= lastGoodStreamId) {
193                     issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
194                 } else if (!sentGoAwayFrame) {
195                     issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
196                 }
197                 return;
198             }
199 
200             // Check if we received a data frame for a stream which is half-closed
201 
202             if (spdySession.isRemoteSideClosed(streamId)) {
203                 spdyDataFrame.release();
204                 issueStreamError(ctx, streamId, SpdyStreamStatus.STREAM_ALREADY_CLOSED);
205                 return;
206             }
207 
208             // Check if we received a data frame before receiving a SYN_REPLY
209             if (!isRemoteInitiatedId(streamId) && !spdySession.hasReceivedReply(streamId)) {
210                 spdyDataFrame.release();
211                 issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
212                 return;
213             }
214 
215             /*
216              * SPDY Data frame flow control processing requirements:
217              *
218              * Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.
219              */
220 
221             // Update receive window size
222             int newWindowSize = spdySession.updateReceiveWindowSize(streamId, deltaWindowSize);
223 
224             // Window size can become negative if we sent a SETTINGS frame that reduces the
225             // size of the transfer window after the peer has written data frames.
226             // The value is bounded by the length that SETTINGS frame decrease the window.
227             // This difference is stored for the session when writing the SETTINGS frame
228             // and is cleared once we send a WINDOW_UPDATE frame.
229             if (newWindowSize < spdySession.getReceiveWindowSizeLowerBound(streamId)) {
230                 spdyDataFrame.release();
231                 issueStreamError(ctx, streamId, SpdyStreamStatus.FLOW_CONTROL_ERROR);
232                 return;
233             }
234 
235             // Window size became negative due to sender writing frame before receiving SETTINGS
236             // Send data frames upstream in initialReceiveWindowSize chunks
237             if (newWindowSize < 0) {
238                 while (spdyDataFrame.content().readableBytes() > initialReceiveWindowSize) {
239                     SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
240                             streamId, spdyDataFrame.content().readRetainedSlice(initialReceiveWindowSize));
241                     ctx.writeAndFlush(partialDataFrame);
242                 }
243             }
244 
245             // Send a WINDOW_UPDATE frame if less than half the stream window size remains
246             if (newWindowSize <= initialReceiveWindowSize / 2 && !spdyDataFrame.isLast()) {
247                 int streamDeltaWindowSize = initialReceiveWindowSize - newWindowSize;
248                 spdySession.updateReceiveWindowSize(streamId, streamDeltaWindowSize);
249                 SpdyWindowUpdateFrame spdyWindowUpdateFrame =
250                         new DefaultSpdyWindowUpdateFrame(streamId, streamDeltaWindowSize);
251                 ctx.writeAndFlush(spdyWindowUpdateFrame);
252             }
253 
254             // Close the remote side of the stream if this is the last frame
255             if (spdyDataFrame.isLast()) {
256                 halfCloseStream(streamId, true, ctx.newSucceededFuture());
257             }
258 
259         } else if (msg instanceof SpdySynStreamFrame) {
260 
261             /*
262              * SPDY SYN_STREAM frame processing requirements:
263              *
264              * If an endpoint receives a SYN_STREAM with a Stream-ID that is less than
265              * any previously received SYN_STREAM, it must issue a session error with
266              * the getStatus PROTOCOL_ERROR.
267              *
268              * If an endpoint receives multiple SYN_STREAM frames with the same active
269              * Stream-ID, it must issue a stream error with the getStatus code PROTOCOL_ERROR.
270              *
271              * The recipient can reject a stream by sending a stream error with the
272              * getStatus code REFUSED_STREAM.
273              */
274 
275             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
276             int streamId = spdySynStreamFrame.streamId();
277 
278             // Check if we received a valid SYN_STREAM frame
279             if (spdySynStreamFrame.isInvalid() ||
280                 !isRemoteInitiatedId(streamId) ||
281                 spdySession.isActiveStream(streamId)) {
282                 issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
283                 return;
284             }
285 
286             // Stream-IDs must be monotonically increasing
287             if (streamId <= lastGoodStreamId) {
288                 issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
289                 return;
290             }
291 
292             // Try to accept the stream
293             byte priority = spdySynStreamFrame.priority();
294             boolean remoteSideClosed = spdySynStreamFrame.isLast();
295             boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
296             if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) {
297                 issueStreamError(ctx, streamId, SpdyStreamStatus.REFUSED_STREAM);
298                 return;
299             }
300 
301         } else if (msg instanceof SpdySynReplyFrame) {
302 
303             /*
304              * SPDY SYN_REPLY frame processing requirements:
305              *
306              * If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID
307              * it must issue a stream error with the getStatus code STREAM_IN_USE.
308              */
309 
310             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
311             int streamId = spdySynReplyFrame.streamId();
312 
313             // Check if we received a valid SYN_REPLY frame
314             if (spdySynReplyFrame.isInvalid() ||
315                 isRemoteInitiatedId(streamId) ||
316                 spdySession.isRemoteSideClosed(streamId)) {
317                 issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
318                 return;
319             }
320 
321             // Check if we have received multiple frames for the same Stream-ID
322             if (spdySession.hasReceivedReply(streamId)) {
323                 issueStreamError(ctx, streamId, SpdyStreamStatus.STREAM_IN_USE);
324                 return;
325             }
326 
327             spdySession.receivedReply(streamId);
328 
329             // Close the remote side of the stream if this is the last frame
330             if (spdySynReplyFrame.isLast()) {
331                 halfCloseStream(streamId, true, ctx.newSucceededFuture());
332             }
333 
334         } else if (msg instanceof SpdyRstStreamFrame) {
335 
336             /*
337              * SPDY RST_STREAM frame processing requirements:
338              *
339              * After receiving a RST_STREAM on a stream, the receiver must not send
340              * additional frames on that stream.
341              *
342              * An endpoint must not send a RST_STREAM in response to a RST_STREAM.
343              */
344 
345             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
346             removeStream(spdyRstStreamFrame.streamId(), ctx.newSucceededFuture());
347 
348         } else if (msg instanceof SpdySettingsFrame) {
349 
350             SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
351 
352             int settingsMinorVersion = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION);
353             if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) {
354                 // Settings frame had the wrong minor version
355                 issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
356                 return;
357             }
358 
359             int newConcurrentStreams =
360                 spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
361             if (newConcurrentStreams >= 0) {
362                 remoteConcurrentStreams = newConcurrentStreams;
363             }
364 
365             // Persistence flag are inconsistent with the use of SETTINGS to communicate
366             // the initial window size. Remove flags from the sender requesting that the
367             // value be persisted. Remove values that the sender indicates are persisted.
368             if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
369                 spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
370             }
371             spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
372 
373             int newInitialWindowSize =
374                 spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
375             if (newInitialWindowSize >= 0) {
376                 updateInitialSendWindowSize(newInitialWindowSize);
377             }
378 
379         } else if (msg instanceof SpdyPingFrame) {
380 
381             /*
382              * SPDY PING frame processing requirements:
383              *
384              * Receivers of a PING frame should send an identical frame to the sender
385              * as soon as possible.
386              *
387              * Receivers of a PING frame must ignore frames that it did not initiate
388              */
389 
390             SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
391 
392             if (isRemoteInitiatedId(spdyPingFrame.id())) {
393                 ctx.writeAndFlush(spdyPingFrame);
394                 return;
395             }
396 
397             // Note: only checks that there are outstanding pings since uniqueness is not enforced
398             if (pings.get() == 0) {
399                 return;
400             }
401             pings.getAndDecrement();
402 
403         } else if (msg instanceof SpdyGoAwayFrame) {
404 
405             receivedGoAwayFrame = true;
406 
407         } else if (msg instanceof SpdyHeadersFrame) {
408 
409             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
410             int streamId = spdyHeadersFrame.streamId();
411 
412             // Check if we received a valid HEADERS frame
413             if (spdyHeadersFrame.isInvalid()) {
414                 issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
415                 return;
416             }
417 
418             if (spdySession.isRemoteSideClosed(streamId)) {
419                 issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
420                 return;
421             }
422 
423             // Close the remote side of the stream if this is the last frame
424             if (spdyHeadersFrame.isLast()) {
425                 halfCloseStream(streamId, true, ctx.newSucceededFuture());
426             }
427 
428         } else if (msg instanceof SpdyWindowUpdateFrame) {
429 
430             /*
431              * SPDY WINDOW_UPDATE frame processing requirements:
432              *
433              * Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31
434              * must send a RST_STREAM with the getStatus code FLOW_CONTROL_ERROR.
435              *
436              * Sender should ignore all WINDOW_UPDATE frames associated with a stream
437              * after sending the last frame for the stream.
438              */
439 
440             SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
441             int streamId = spdyWindowUpdateFrame.streamId();
442             int deltaWindowSize = spdyWindowUpdateFrame.deltaWindowSize();
443 
444             // Ignore frames for half-closed streams
445             if (streamId != SPDY_SESSION_STREAM_ID && spdySession.isLocalSideClosed(streamId)) {
446                 return;
447             }
448 
449             // Check for numerical overflow
450             if (spdySession.getSendWindowSize(streamId) > Integer.MAX_VALUE - deltaWindowSize) {
451                 if (streamId == SPDY_SESSION_STREAM_ID) {
452                     issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
453                 } else {
454                     issueStreamError(ctx, streamId, SpdyStreamStatus.FLOW_CONTROL_ERROR);
455                 }
456                 return;
457             }
458 
459             updateSendWindowSize(ctx, streamId, deltaWindowSize);
460         }
461 
462         ctx.fireChannelRead(msg);
463     }
464 
465     @Override
466     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
467         for (Integer streamId: spdySession.activeStreams().keySet()) {
468             removeStream(streamId, ctx.newSucceededFuture());
469         }
470         ctx.fireChannelInactive();
471     }
472 
473     @Override
474     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
475         if (cause instanceof SpdyProtocolException) {
476             issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
477         }
478 
479         ctx.fireExceptionCaught(cause);
480     }
481 
482     @Override
483     public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
484         sendGoAwayFrame(ctx, promise);
485     }
486 
487     @Override
488     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
489         if (msg instanceof SpdyDataFrame ||
490             msg instanceof SpdySynStreamFrame ||
491             msg instanceof SpdySynReplyFrame ||
492             msg instanceof SpdyRstStreamFrame ||
493             msg instanceof SpdySettingsFrame ||
494             msg instanceof SpdyPingFrame ||
495             msg instanceof SpdyGoAwayFrame ||
496             msg instanceof SpdyHeadersFrame ||
497             msg instanceof SpdyWindowUpdateFrame) {
498 
499             handleOutboundMessage(ctx, msg, promise);
500         } else {
501             ctx.write(msg, promise);
502         }
503     }
504 
505     private void handleOutboundMessage(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
506         if (msg instanceof SpdyDataFrame) {
507 
508             SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
509             int streamId = spdyDataFrame.streamId();
510 
511             // Frames must not be sent on half-closed streams
512             if (spdySession.isLocalSideClosed(streamId)) {
513                 spdyDataFrame.release();
514                 promise.setFailure(PROTOCOL_EXCEPTION);
515                 return;
516             }
517 
518             /*
519              * SPDY Data frame flow control processing requirements:
520              *
521              * Sender must not send a data frame with data length greater
522              * than the transfer window size.
523              *
524              * After sending each data frame, the sender decrements its
525              * transfer window size by the amount of data transmitted.
526              *
527              * When the window size becomes less than or equal to 0, the
528              * sender must pause transmitting data frames.
529              */
530 
531             int dataLength = spdyDataFrame.content().readableBytes();
532             int sendWindowSize = spdySession.getSendWindowSize(streamId);
533             int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID);
534             sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize);
535 
536             if (sendWindowSize <= 0) {
537                 // Stream is stalled -- enqueue Data frame and return
538                 spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise));
539                 return;
540             } else if (sendWindowSize < dataLength) {
541                 // Stream is not stalled but we cannot send the entire frame
542                 spdySession.updateSendWindowSize(streamId, -1 * sendWindowSize);
543                 spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize);
544 
545                 // Create a partial data frame whose length is the current window size
546                 SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
547                         streamId, spdyDataFrame.content().readRetainedSlice(sendWindowSize));
548 
549                 // Enqueue the remaining data (will be the first frame queued)
550                 spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise));
551 
552                 // The transfer window size is pre-decremented when sending a data frame downstream.
553                 // Close the session on write failures that leave the transfer window in a corrupt state.
554                 final ChannelHandlerContext context = ctx;
555                 ctx.write(partialDataFrame).addListener(future -> {
556                     if (!future.isSuccess()) {
557                         issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR);
558                     }
559                 });
560                 return;
561             } else {
562                 // Window size is large enough to send entire data frame
563                 spdySession.updateSendWindowSize(streamId, -1 * dataLength);
564                 spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataLength);
565 
566                 // The transfer window size is pre-decremented when sending a data frame downstream.
567                 // Close the session on write failures that leave the transfer window in a corrupt state.
568                 final ChannelHandlerContext context = ctx;
569                 promise.addListener(future -> {
570                     if (!future.isSuccess()) {
571                         issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR);
572                     }
573                 });
574             }
575 
576             // Close the local side of the stream if this is the last frame
577             if (spdyDataFrame.isLast()) {
578                 halfCloseStream(streamId, false, promise);
579             }
580 
581         } else if (msg instanceof SpdySynStreamFrame) {
582 
583             SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
584             int streamId = spdySynStreamFrame.streamId();
585 
586             if (isRemoteInitiatedId(streamId)) {
587                 promise.setFailure(PROTOCOL_EXCEPTION);
588                 return;
589             }
590 
591             byte priority = spdySynStreamFrame.priority();
592             boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional();
593             boolean localSideClosed = spdySynStreamFrame.isLast();
594             if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) {
595                 promise.setFailure(PROTOCOL_EXCEPTION);
596                 return;
597             }
598 
599         } else if (msg instanceof SpdySynReplyFrame) {
600 
601             SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
602             int streamId = spdySynReplyFrame.streamId();
603 
604             // Frames must not be sent on half-closed streams
605             if (!isRemoteInitiatedId(streamId) || spdySession.isLocalSideClosed(streamId)) {
606                 promise.setFailure(PROTOCOL_EXCEPTION);
607                 return;
608             }
609 
610             // Close the local side of the stream if this is the last frame
611             if (spdySynReplyFrame.isLast()) {
612                 halfCloseStream(streamId, false, promise);
613             }
614 
615         } else if (msg instanceof SpdyRstStreamFrame) {
616 
617             SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
618             removeStream(spdyRstStreamFrame.streamId(), promise);
619 
620         } else if (msg instanceof SpdySettingsFrame) {
621 
622             SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
623 
624             int settingsMinorVersion = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION);
625             if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) {
626                 // Settings frame had the wrong minor version
627                 promise.setFailure(PROTOCOL_EXCEPTION);
628                 return;
629             }
630 
631             int newConcurrentStreams =
632                     spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
633             if (newConcurrentStreams >= 0) {
634                 localConcurrentStreams = newConcurrentStreams;
635             }
636 
637             // Persistence flag are inconsistent with the use of SETTINGS to communicate
638             // the initial window size. Remove flags from the sender requesting that the
639             // value be persisted. Remove values that the sender indicates are persisted.
640             if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
641                 spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
642             }
643             spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);
644 
645             int newInitialWindowSize =
646                     spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
647             if (newInitialWindowSize >= 0) {
648                 updateInitialReceiveWindowSize(newInitialWindowSize);
649             }
650 
651         } else if (msg instanceof SpdyPingFrame) {
652 
653             SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
654             if (isRemoteInitiatedId(spdyPingFrame.id())) {
655                 ctx.fireExceptionCaught(new IllegalArgumentException(
656                             "invalid PING ID: " + spdyPingFrame.id()));
657                 return;
658             }
659             pings.getAndIncrement();
660 
661         } else if (msg instanceof SpdyGoAwayFrame) {
662 
663             // Why is this being sent? Intercept it and fail the write.
664             // Should have sent a CLOSE ChannelStateEvent
665             promise.setFailure(PROTOCOL_EXCEPTION);
666             return;
667 
668         } else if (msg instanceof SpdyHeadersFrame) {
669 
670             SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
671             int streamId = spdyHeadersFrame.streamId();
672 
673             // Frames must not be sent on half-closed streams
674             if (spdySession.isLocalSideClosed(streamId)) {
675                 promise.setFailure(PROTOCOL_EXCEPTION);
676                 return;
677             }
678 
679             // Close the local side of the stream if this is the last frame
680             if (spdyHeadersFrame.isLast()) {
681                 halfCloseStream(streamId, false, promise);
682             }
683 
684         } else if (msg instanceof SpdyWindowUpdateFrame) {
685 
686             // Why is this being sent? Intercept it and fail the write.
687             promise.setFailure(PROTOCOL_EXCEPTION);
688             return;
689         }
690 
691         ctx.write(msg, promise);
692     }
693 
694     /*
695      * SPDY Session Error Handling:
696      *
697      * When a session error occurs, the endpoint encountering the error must first
698      * send a GOAWAY frame with the Stream-ID of the most recently received stream
699      * from the remote endpoint, and the error code for why the session is terminating.
700      *
701      * After sending the GOAWAY frame, the endpoint must close the TCP connection.
702      */
703     private void issueSessionError(
704             ChannelHandlerContext ctx, SpdySessionStatus status) {
705 
706         sendGoAwayFrame(ctx, status).addListener(new ClosingChannelFutureListener(ctx, ctx.newPromise()));
707     }
708 
709     /*
710      * SPDY Stream Error Handling:
711      *
712      * Upon a stream error, the endpoint must send a RST_STREAM frame which contains
713      * the Stream-ID for the stream where the error occurred and the error getStatus which
714      * caused the error.
715      *
716      * After sending the RST_STREAM, the stream is closed to the sending endpoint.
717      *
718      * Note: this is only called by the worker thread
719      */
720     private void issueStreamError(ChannelHandlerContext ctx, int streamId, SpdyStreamStatus status) {
721         boolean fireChannelRead = !spdySession.isRemoteSideClosed(streamId);
722         ChannelPromise promise = ctx.newPromise();
723         removeStream(streamId, promise);
724 
725         SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, status);
726         ctx.writeAndFlush(spdyRstStreamFrame, promise);
727         if (fireChannelRead) {
728             ctx.fireChannelRead(spdyRstStreamFrame);
729         }
730     }
731 
732     /*
733      * Helper functions
734      */
735 
736     private boolean isRemoteInitiatedId(int id) {
737         boolean serverId = isServerId(id);
738         return server && !serverId || !server && serverId;
739     }
740 
741     // need to synchronize to prevent new streams from being created while updating active streams
742     private void updateInitialSendWindowSize(int newInitialWindowSize) {
743         int deltaWindowSize = newInitialWindowSize - initialSendWindowSize;
744         initialSendWindowSize = newInitialWindowSize;
745         spdySession.updateAllSendWindowSizes(deltaWindowSize);
746     }
747 
748     // need to synchronize to prevent new streams from being created while updating active streams
749     private void updateInitialReceiveWindowSize(int newInitialWindowSize) {
750         int deltaWindowSize = newInitialWindowSize - initialReceiveWindowSize;
751         initialReceiveWindowSize = newInitialWindowSize;
752         spdySession.updateAllReceiveWindowSizes(deltaWindowSize);
753     }
754 
755     // need to synchronize accesses to sentGoAwayFrame, lastGoodStreamId, and initial window sizes
756     private boolean acceptStream(
757             int streamId, byte priority, boolean remoteSideClosed, boolean localSideClosed) {
758         // Cannot initiate any new streams after receiving or sending GOAWAY
759         if (receivedGoAwayFrame || sentGoAwayFrame) {
760             return false;
761         }
762 
763         boolean remote = isRemoteInitiatedId(streamId);
764         int maxConcurrentStreams = remote ? localConcurrentStreams : remoteConcurrentStreams;
765         if (spdySession.numActiveStreams(remote) >= maxConcurrentStreams) {
766             return false;
767         }
768         spdySession.acceptStream(
769                 streamId, priority, remoteSideClosed, localSideClosed,
770                 initialSendWindowSize, initialReceiveWindowSize, remote);
771         if (remote) {
772             lastGoodStreamId = streamId;
773         }
774         return true;
775     }
776 
777     private void halfCloseStream(int streamId, boolean remote, ChannelFuture future) {
778         if (remote) {
779             spdySession.closeRemoteSide(streamId, isRemoteInitiatedId(streamId));
780         } else {
781             spdySession.closeLocalSide(streamId, isRemoteInitiatedId(streamId));
782         }
783         if (closeSessionFutureListener != null && spdySession.noActiveStreams()) {
784             future.addListener(closeSessionFutureListener);
785         }
786     }
787 
788     private void removeStream(int streamId, ChannelFuture future) {
789         spdySession.removeStream(streamId, STREAM_CLOSED, isRemoteInitiatedId(streamId));
790 
791         if (closeSessionFutureListener != null && spdySession.noActiveStreams()) {
792             future.addListener(closeSessionFutureListener);
793         }
794     }
795 
796     private void updateSendWindowSize(final ChannelHandlerContext ctx, int streamId, int deltaWindowSize) {
797         spdySession.updateSendWindowSize(streamId, deltaWindowSize);
798 
799         while (true) {
800             // Check if we have unblocked a stalled stream
801             SpdySession.PendingWrite pendingWrite = spdySession.getPendingWrite(streamId);
802             if (pendingWrite == null) {
803                 return;
804             }
805 
806             SpdyDataFrame spdyDataFrame = pendingWrite.spdyDataFrame;
807             int dataFrameSize = spdyDataFrame.content().readableBytes();
808             int writeStreamId = spdyDataFrame.streamId();
809             int sendWindowSize = spdySession.getSendWindowSize(writeStreamId);
810             int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID);
811             sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize);
812 
813             if (sendWindowSize <= 0) {
814                 return;
815             } else if (sendWindowSize < dataFrameSize) {
816                 // We can send a partial frame
817                 spdySession.updateSendWindowSize(writeStreamId, -1 * sendWindowSize);
818                 spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize);
819 
820                 // Create a partial data frame whose length is the current window size
821                 SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame(
822                         writeStreamId, spdyDataFrame.content().readRetainedSlice(sendWindowSize));
823 
824                 // The transfer window size is pre-decremented when sending a data frame downstream.
825                 ctx.writeAndFlush(partialDataFrame).addListener(future -> {
826                     if (!future.isSuccess()) {
827                         issueSessionError(ctx, SpdySessionStatus.INTERNAL_ERROR);
828                     }
829                 });
830             } else {
831                 // Window size is large enough to send entire data frame
832                 spdySession.removePendingWrite(writeStreamId);
833                 spdySession.updateSendWindowSize(writeStreamId, -1 * dataFrameSize);
834                 spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataFrameSize);
835 
836                 // Close the local side of the stream if this is the last frame
837                 if (spdyDataFrame.isLast()) {
838                     halfCloseStream(writeStreamId, false, pendingWrite.promise);
839                 }
840 
841                 // The transfer window size is pre-decremented when sending a data frame downstream.
842                 // Close the session on write failures that leave the transfer window in a corrupt state.
843                 ctx.writeAndFlush(spdyDataFrame, pendingWrite.promise).addListener(future -> {
844                     if (!future.isSuccess()) {
845                         issueSessionError(ctx, SpdySessionStatus.INTERNAL_ERROR);
846                     }
847                 });
848             }
849         }
850     }
851 
852     private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelPromise future) {
853         // Avoid NotYetConnectedException
854         if (!ctx.channel().isActive()) {
855             ctx.close(future);
856             return;
857         }
858 
859         ChannelFuture f = sendGoAwayFrame(ctx, SpdySessionStatus.OK);
860         if (spdySession.noActiveStreams()) {
861             f.addListener(new ClosingChannelFutureListener(ctx, future));
862         } else {
863             closeSessionFutureListener = new ClosingChannelFutureListener(ctx, future);
864         }
865         // FIXME: Close the connection forcibly after timeout.
866     }
867 
868     private ChannelFuture sendGoAwayFrame(
869             ChannelHandlerContext ctx, SpdySessionStatus status) {
870         if (!sentGoAwayFrame) {
871             sentGoAwayFrame = true;
872             SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamId, status);
873             return ctx.writeAndFlush(spdyGoAwayFrame);
874         } else {
875             return ctx.newSucceededFuture();
876         }
877     }
878 
879     private static final class ClosingChannelFutureListener implements ChannelFutureListener {
880         private final ChannelHandlerContext ctx;
881         private final ChannelPromise promise;
882 
883         ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelPromise promise) {
884             this.ctx = ctx;
885             this.promise = promise;
886         }
887 
888         @Override
889         public void operationComplete(ChannelFuture sentGoAwayFuture) throws Exception {
890             ctx.close(promise);
891         }
892     }
893 }