Example #1
0
  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (msg instanceof SpdyDataFrame) {

      /*
       * SPDY Data frame processing requirements:
       *
       * If an endpoint receives a data frame for a Stream-ID which is not open
       * and the endpoint has not sent a GOAWAY frame, it must issue a stream error
       * with the error code INVALID_STREAM for the Stream-ID.
       *
       * If an endpoint which created the stream receives a data frame before receiving
       * a SYN_REPLY on that stream, it is a protocol error, and the recipient must
       * issue a stream error with the getStatus code PROTOCOL_ERROR for the Stream-ID.
       *
       * If an endpoint receives multiple data frames for invalid Stream-IDs,
       * it may close the session.
       *
       * If an endpoint refuses a stream it must ignore any data frames for that stream.
       *
       * If an endpoint receives a data frame after the stream is half-closed from the
       * sender, it must send a RST_STREAM frame with the getStatus STREAM_ALREADY_CLOSED.
       *
       * If an endpoint receives a data frame after the stream is closed, it must send
       * a RST_STREAM frame with the getStatus PROTOCOL_ERROR.
       */
      SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
      int streamId = spdyDataFrame.streamId();

      int deltaWindowSize = -1 * spdyDataFrame.content().readableBytes();
      int newSessionWindowSize =
          spdySession.updateReceiveWindowSize(SPDY_SESSION_STREAM_ID, deltaWindowSize);

      // Check if session window size is reduced beyond allowable lower bound
      if (newSessionWindowSize < 0) {
        issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
        return;
      }

      // Send a WINDOW_UPDATE frame if less than half the session window size remains
      if (newSessionWindowSize <= initialSessionReceiveWindowSize / 2) {
        int sessionDeltaWindowSize = initialSessionReceiveWindowSize - newSessionWindowSize;
        spdySession.updateReceiveWindowSize(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
        SpdyWindowUpdateFrame spdyWindowUpdateFrame =
            new DefaultSpdyWindowUpdateFrame(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
        ctx.writeAndFlush(spdyWindowUpdateFrame);
      }

      // Check if we received a data frame for a Stream-ID which is not open

      if (!spdySession.isActiveStream(streamId)) {
        spdyDataFrame.release();
        if (streamId <= lastGoodStreamId) {
          issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
        } else if (!sentGoAwayFrame) {
          issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
        }
        return;
      }

      // Check if we received a data frame for a stream which is half-closed

      if (spdySession.isRemoteSideClosed(streamId)) {
        spdyDataFrame.release();
        issueStreamError(ctx, streamId, SpdyStreamStatus.STREAM_ALREADY_CLOSED);
        return;
      }

      // Check if we received a data frame before receiving a SYN_REPLY
      if (!isRemoteInitiatedId(streamId) && !spdySession.hasReceivedReply(streamId)) {
        spdyDataFrame.release();
        issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
        return;
      }

      /*
       * SPDY Data frame flow control processing requirements:
       *
       * Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.
       */

      // Update receive window size
      int newWindowSize = spdySession.updateReceiveWindowSize(streamId, deltaWindowSize);

      // Window size can become negative if we sent a SETTINGS frame that reduces the
      // size of the transfer window after the peer has written data frames.
      // The value is bounded by the length that SETTINGS frame decrease the window.
      // This difference is stored for the session when writing the SETTINGS frame
      // and is cleared once we send a WINDOW_UPDATE frame.
      if (newWindowSize < spdySession.getReceiveWindowSizeLowerBound(streamId)) {
        spdyDataFrame.release();
        issueStreamError(ctx, streamId, SpdyStreamStatus.FLOW_CONTROL_ERROR);
        return;
      }

      // Window size became negative due to sender writing frame before receiving SETTINGS
      // Send data frames upstream in initialReceiveWindowSize chunks
      if (newWindowSize < 0) {
        while (spdyDataFrame.content().readableBytes() > initialReceiveWindowSize) {
          SpdyDataFrame partialDataFrame =
              new DefaultSpdyDataFrame(
                  streamId, spdyDataFrame.content().readSlice(initialReceiveWindowSize).retain());
          ctx.writeAndFlush(partialDataFrame);
        }
      }

      // Send a WINDOW_UPDATE frame if less than half the stream window size remains
      if (newWindowSize <= initialReceiveWindowSize / 2 && !spdyDataFrame.isLast()) {
        int streamDeltaWindowSize = initialReceiveWindowSize - newWindowSize;
        spdySession.updateReceiveWindowSize(streamId, streamDeltaWindowSize);
        SpdyWindowUpdateFrame spdyWindowUpdateFrame =
            new DefaultSpdyWindowUpdateFrame(streamId, streamDeltaWindowSize);
        ctx.writeAndFlush(spdyWindowUpdateFrame);
      }

      // Close the remote side of the stream if this is the last frame
      if (spdyDataFrame.isLast()) {
        halfCloseStream(streamId, true, ctx.newSucceededFuture());
      }

    } else if (msg instanceof SpdySynStreamFrame) {

      /*
       * SPDY SYN_STREAM frame processing requirements:
       *
       * If an endpoint receives a SYN_STREAM with a Stream-ID that is less than
       * any previously received SYN_STREAM, it must issue a session error with
       * the getStatus PROTOCOL_ERROR.
       *
       * If an endpoint receives multiple SYN_STREAM frames with the same active
       * Stream-ID, it must issue a stream error with the getStatus code PROTOCOL_ERROR.
       *
       * The recipient can reject a stream by sending a stream error with the
       * getStatus code REFUSED_STREAM.
       */

      SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
      int streamId = spdySynStreamFrame.streamId();

      // Check if we received a valid SYN_STREAM frame
      if (spdySynStreamFrame.isInvalid()
          || !isRemoteInitiatedId(streamId)
          || spdySession.isActiveStream(streamId)) {
        issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
        return;
      }

      // Stream-IDs must be monotonically increasing
      if (streamId <= lastGoodStreamId) {
        issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
        return;
      }

      // Try to accept the stream
      byte priority = spdySynStreamFrame.priority();
      boolean remoteSideClosed = spdySynStreamFrame.isLast();
      boolean localSideClosed = spdySynStreamFrame.isUnidirectional();
      if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) {
        issueStreamError(ctx, streamId, SpdyStreamStatus.REFUSED_STREAM);
        return;
      }

    } else if (msg instanceof SpdySynReplyFrame) {

      /*
       * SPDY SYN_REPLY frame processing requirements:
       *
       * If an endpoint receives multiple SYN_REPLY frames for the same active Stream-ID
       * it must issue a stream error with the getStatus code STREAM_IN_USE.
       */

      SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
      int streamId = spdySynReplyFrame.streamId();

      // Check if we received a valid SYN_REPLY frame
      if (spdySynReplyFrame.isInvalid()
          || isRemoteInitiatedId(streamId)
          || spdySession.isRemoteSideClosed(streamId)) {
        issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
        return;
      }

      // Check if we have received multiple frames for the same Stream-ID
      if (spdySession.hasReceivedReply(streamId)) {
        issueStreamError(ctx, streamId, SpdyStreamStatus.STREAM_IN_USE);
        return;
      }

      spdySession.receivedReply(streamId);

      // Close the remote side of the stream if this is the last frame
      if (spdySynReplyFrame.isLast()) {
        halfCloseStream(streamId, true, ctx.newSucceededFuture());
      }

    } else if (msg instanceof SpdyRstStreamFrame) {

      /*
       * SPDY RST_STREAM frame processing requirements:
       *
       * After receiving a RST_STREAM on a stream, the receiver must not send
       * additional frames on that stream.
       *
       * An endpoint must not send a RST_STREAM in response to a RST_STREAM.
       */

      SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
      removeStream(spdyRstStreamFrame.streamId(), ctx.newSucceededFuture());

    } else if (msg instanceof SpdySettingsFrame) {

      SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;

      int settingsMinorVersion =
          spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION);
      if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) {
        // Settings frame had the wrong minor version
        issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
        return;
      }

      int newConcurrentStreams =
          spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
      if (newConcurrentStreams >= 0) {
        remoteConcurrentStreams = newConcurrentStreams;
      }

      // Persistence flag are inconsistent with the use of SETTINGS to communicate
      // the initial window size. Remove flags from the sender requesting that the
      // value be persisted. Remove values that the sender indicates are persisted.
      if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
        spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
      }
      spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);

      int newInitialWindowSize =
          spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
      if (newInitialWindowSize >= 0) {
        updateInitialSendWindowSize(newInitialWindowSize);
      }

    } else if (msg instanceof SpdyPingFrame) {

      /*
       * SPDY PING frame processing requirements:
       *
       * Receivers of a PING frame should send an identical frame to the sender
       * as soon as possible.
       *
       * Receivers of a PING frame must ignore frames that it did not initiate
       */

      SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;

      if (isRemoteInitiatedId(spdyPingFrame.id())) {
        ctx.writeAndFlush(spdyPingFrame);
        return;
      }

      // Note: only checks that there are outstanding pings since uniqueness is not enforced
      if (pings.get() == 0) {
        return;
      }
      pings.getAndDecrement();

    } else if (msg instanceof SpdyGoAwayFrame) {

      receivedGoAwayFrame = true;

    } else if (msg instanceof SpdyHeadersFrame) {

      SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
      int streamId = spdyHeadersFrame.streamId();

      // Check if we received a valid HEADERS frame
      if (spdyHeadersFrame.isInvalid()) {
        issueStreamError(ctx, streamId, SpdyStreamStatus.PROTOCOL_ERROR);
        return;
      }

      if (spdySession.isRemoteSideClosed(streamId)) {
        issueStreamError(ctx, streamId, SpdyStreamStatus.INVALID_STREAM);
        return;
      }

      // Close the remote side of the stream if this is the last frame
      if (spdyHeadersFrame.isLast()) {
        halfCloseStream(streamId, true, ctx.newSucceededFuture());
      }

    } else if (msg instanceof SpdyWindowUpdateFrame) {

      /*
       * SPDY WINDOW_UPDATE frame processing requirements:
       *
       * Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31
       * must send a RST_STREAM with the getStatus code FLOW_CONTROL_ERROR.
       *
       * Sender should ignore all WINDOW_UPDATE frames associated with a stream
       * after sending the last frame for the stream.
       */

      SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
      int streamId = spdyWindowUpdateFrame.streamId();
      int deltaWindowSize = spdyWindowUpdateFrame.deltaWindowSize();

      // Ignore frames for half-closed streams
      if (streamId != SPDY_SESSION_STREAM_ID && spdySession.isLocalSideClosed(streamId)) {
        return;
      }

      // Check for numerical overflow
      if (spdySession.getSendWindowSize(streamId) > Integer.MAX_VALUE - deltaWindowSize) {
        if (streamId == SPDY_SESSION_STREAM_ID) {
          issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR);
        } else {
          issueStreamError(ctx, streamId, SpdyStreamStatus.FLOW_CONTROL_ERROR);
        }
        return;
      }

      updateSendWindowSize(ctx, streamId, deltaWindowSize);
    }

    ctx.fireChannelRead(msg);
  }
Example #2
0
  private void handleOutboundMessage(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
      throws Exception {
    if (msg instanceof SpdyDataFrame) {

      SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
      int streamId = spdyDataFrame.streamId();

      // Frames must not be sent on half-closed streams
      if (spdySession.isLocalSideClosed(streamId)) {
        spdyDataFrame.release();
        promise.setFailure(PROTOCOL_EXCEPTION);
        return;
      }

      /*
       * SPDY Data frame flow control processing requirements:
       *
       * Sender must not send a data frame with data length greater
       * than the transfer window size.
       *
       * After sending each data frame, the sender decrements its
       * transfer window size by the amount of data transmitted.
       *
       * When the window size becomes less than or equal to 0, the
       * sender must pause transmitting data frames.
       */

      int dataLength = spdyDataFrame.content().readableBytes();
      int sendWindowSize = spdySession.getSendWindowSize(streamId);
      int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID);
      sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize);

      if (sendWindowSize <= 0) {
        // Stream is stalled -- enqueue Data frame and return
        spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise));
        return;
      } else if (sendWindowSize < dataLength) {
        // Stream is not stalled but we cannot send the entire frame
        spdySession.updateSendWindowSize(streamId, -1 * sendWindowSize);
        spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize);

        // Create a partial data frame whose length is the current window size
        SpdyDataFrame partialDataFrame =
            new DefaultSpdyDataFrame(
                streamId, spdyDataFrame.content().readSlice(sendWindowSize).retain());

        // Enqueue the remaining data (will be the first frame queued)
        spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise));

        // The transfer window size is pre-decremented when sending a data frame downstream.
        // Close the session on write failures that leave the transfer window in a corrupt state.
        final ChannelHandlerContext context = ctx;
        ctx.write(partialDataFrame)
            .addListener(
                new ChannelFutureListener() {
                  @Override
                  public void operationComplete(ChannelFuture future) throws Exception {
                    if (!future.isSuccess()) {
                      issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR);
                    }
                  }
                });
        return;
      } else {
        // Window size is large enough to send entire data frame
        spdySession.updateSendWindowSize(streamId, -1 * dataLength);
        spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataLength);

        // The transfer window size is pre-decremented when sending a data frame downstream.
        // Close the session on write failures that leave the transfer window in a corrupt state.
        final ChannelHandlerContext context = ctx;
        promise.addListener(
            new ChannelFutureListener() {
              @Override
              public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                  issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR);
                }
              }
            });
      }

      // Close the local side of the stream if this is the last frame
      if (spdyDataFrame.isLast()) {
        halfCloseStream(streamId, false, promise);
      }

    } else if (msg instanceof SpdySynStreamFrame) {

      SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
      int streamId = spdySynStreamFrame.streamId();

      if (isRemoteInitiatedId(streamId)) {
        promise.setFailure(PROTOCOL_EXCEPTION);
        return;
      }

      byte priority = spdySynStreamFrame.priority();
      boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional();
      boolean localSideClosed = spdySynStreamFrame.isLast();
      if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) {
        promise.setFailure(PROTOCOL_EXCEPTION);
        return;
      }

    } else if (msg instanceof SpdySynReplyFrame) {

      SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
      int streamId = spdySynReplyFrame.streamId();

      // Frames must not be sent on half-closed streams
      if (!isRemoteInitiatedId(streamId) || spdySession.isLocalSideClosed(streamId)) {
        promise.setFailure(PROTOCOL_EXCEPTION);
        return;
      }

      // Close the local side of the stream if this is the last frame
      if (spdySynReplyFrame.isLast()) {
        halfCloseStream(streamId, false, promise);
      }

    } else if (msg instanceof SpdyRstStreamFrame) {

      SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
      removeStream(spdyRstStreamFrame.streamId(), promise);

    } else if (msg instanceof SpdySettingsFrame) {

      SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;

      int settingsMinorVersion =
          spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION);
      if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) {
        // Settings frame had the wrong minor version
        promise.setFailure(PROTOCOL_EXCEPTION);
        return;
      }

      int newConcurrentStreams =
          spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);
      if (newConcurrentStreams >= 0) {
        localConcurrentStreams = newConcurrentStreams;
      }

      // Persistence flag are inconsistent with the use of SETTINGS to communicate
      // the initial window size. Remove flags from the sender requesting that the
      // value be persisted. Remove values that the sender indicates are persisted.
      if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) {
        spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
      }
      spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false);

      int newInitialWindowSize =
          spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);
      if (newInitialWindowSize >= 0) {
        updateInitialReceiveWindowSize(newInitialWindowSize);
      }

    } else if (msg instanceof SpdyPingFrame) {

      SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
      if (isRemoteInitiatedId(spdyPingFrame.id())) {
        ctx.fireExceptionCaught(
            new IllegalArgumentException("invalid PING ID: " + spdyPingFrame.id()));
        return;
      }
      pings.getAndIncrement();

    } else if (msg instanceof SpdyGoAwayFrame) {

      // Why is this being sent? Intercept it and fail the write.
      // Should have sent a CLOSE ChannelStateEvent
      promise.setFailure(PROTOCOL_EXCEPTION);
      return;

    } else if (msg instanceof SpdyHeadersFrame) {

      SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
      int streamId = spdyHeadersFrame.streamId();

      // Frames must not be sent on half-closed streams
      if (spdySession.isLocalSideClosed(streamId)) {
        promise.setFailure(PROTOCOL_EXCEPTION);
        return;
      }

      // Close the local side of the stream if this is the last frame
      if (spdyHeadersFrame.isLast()) {
        halfCloseStream(streamId, false, promise);
      }

    } else if (msg instanceof SpdyWindowUpdateFrame) {

      // Why is this being sent? Intercept it and fail the write.
      promise.setFailure(PROTOCOL_EXCEPTION);
      return;
    }

    ctx.write(msg, promise);
  }