@Override
 public void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
   try {
     decode(ctx, in, out);
   } finally {
     headerBlockDecoder.end();
   }
 }
 /** Creates a new instance with the specified parameters. */
 public SpdyFrameDecoder(SpdyVersion version, int maxChunkSize, int maxHeaderSize) {
   this(version, maxChunkSize, SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize));
 }
  @Override
  protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out)
      throws Exception {
    switch (state) {
      case READ_COMMON_HEADER:
        state = readCommonHeader(buffer);
        if (state == State.FRAME_ERROR) {
          if (version != spdyVersion) {
            fireProtocolException(ctx, "Unsupported version: " + version);
          } else {
            fireInvalidFrameException(ctx);
          }
        }

        // FrameDecoders must consume data when producing frames
        // All length 0 frames must be generated now
        if (length == 0) {
          if (state == State.READ_DATA_FRAME) {
            SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId);
            spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
            state = State.READ_COMMON_HEADER;
            out.add(spdyDataFrame);
            return;
          }
          // There are no length 0 control frames
          state = State.READ_COMMON_HEADER;
        }

        return;

      case READ_CONTROL_FRAME:
        try {
          Object frame = readControlFrame(buffer);
          if (frame != null) {
            state = State.READ_COMMON_HEADER;
            out.add(frame);
          }
          return;
        } catch (IllegalArgumentException e) {
          state = State.FRAME_ERROR;
          fireInvalidFrameException(ctx);
        }
        return;

      case READ_SETTINGS_FRAME:
        if (spdySettingsFrame == null) {
          // Validate frame length against number of entries
          if (buffer.readableBytes() < 4) {
            return;
          }
          int numEntries = getUnsignedInt(buffer, buffer.readerIndex());
          buffer.skipBytes(4);
          length -= 4;

          // Each ID/Value entry is 8 bytes
          if ((length & 0x07) != 0 || length >> 3 != numEntries) {
            state = State.FRAME_ERROR;
            fireInvalidFrameException(ctx);
            return;
          }

          spdySettingsFrame = new DefaultSpdySettingsFrame();

          boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0;
          spdySettingsFrame.setClearPreviouslyPersistedSettings(clear);
        }

        int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
        for (int i = 0; i < readableEntries; i++) {
          byte ID_flags = buffer.getByte(buffer.readerIndex());
          int ID = getUnsignedMedium(buffer, buffer.readerIndex() + 1);
          int value = getSignedInt(buffer, buffer.readerIndex() + 4);
          buffer.skipBytes(8);

          if (!spdySettingsFrame.isSet(ID)) {
            boolean persistVal = (ID_flags & SPDY_SETTINGS_PERSIST_VALUE) != 0;
            boolean persisted = (ID_flags & SPDY_SETTINGS_PERSISTED) != 0;
            spdySettingsFrame.setValue(ID, value, persistVal, persisted);
          }
        }

        length -= 8 * readableEntries;
        if (length == 0) {
          state = State.READ_COMMON_HEADER;
          Object frame = spdySettingsFrame;
          spdySettingsFrame = null;
          out.add(frame);
          return;
        }
        return;

      case READ_HEADER_BLOCK_FRAME:
        try {
          spdyHeadersFrame = readHeaderBlockFrame(buffer);
          if (spdyHeadersFrame != null) {
            if (length == 0) {
              state = State.READ_COMMON_HEADER;
              Object frame = spdyHeadersFrame;
              spdyHeadersFrame = null;
              out.add(frame);
              return;
            }
            state = State.READ_HEADER_BLOCK;
          }
          return;
        } catch (IllegalArgumentException e) {
          state = State.FRAME_ERROR;
          fireInvalidFrameException(ctx);
          return;
        }

      case READ_HEADER_BLOCK:
        int compressedBytes = Math.min(buffer.readableBytes(), length);
        ByteBuf compressed = buffer.slice(buffer.readerIndex(), compressedBytes);

        try {
          headerBlockDecoder.decode(compressed, spdyHeadersFrame);
        } catch (Exception e) {
          state = State.FRAME_ERROR;
          spdyHeadersFrame = null;
          ctx.fireExceptionCaught(e);
          return;
        }

        int readBytes = compressedBytes - compressed.readableBytes();
        buffer.skipBytes(readBytes);
        length -= readBytes;

        if (spdyHeadersFrame != null
            && (spdyHeadersFrame.isInvalid() || spdyHeadersFrame.isTruncated())) {

          Object frame = spdyHeadersFrame;
          spdyHeadersFrame = null;
          if (length == 0) {
            headerBlockDecoder.reset();
            state = State.READ_COMMON_HEADER;
          }
          out.add(frame);
          return;
        }

        if (length == 0) {
          Object frame = spdyHeadersFrame;
          spdyHeadersFrame = null;
          headerBlockDecoder.reset();
          state = State.READ_COMMON_HEADER;
          if (frame != null) {
            out.add(frame);
          }
        }
        return;

      case READ_DATA_FRAME:
        if (streamId == 0) {
          state = State.FRAME_ERROR;
          fireProtocolException(ctx, "Received invalid data frame");
          return;
        }

        // Generate data frames that do not exceed maxChunkSize
        int dataLength = Math.min(maxChunkSize, length);

        // Wait until entire frame is readable
        if (buffer.readableBytes() < dataLength) {
          return;
        }

        ByteBuf data = ctx.alloc().buffer(dataLength);
        data.writeBytes(buffer, dataLength);
        SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
        length -= dataLength;

        if (length == 0) {
          spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
          state = State.READ_COMMON_HEADER;
        }
        out.add(spdyDataFrame);
        return;

      case DISCARD_FRAME:
        int numBytes = Math.min(buffer.readableBytes(), length);
        buffer.skipBytes(numBytes);
        length -= numBytes;
        if (length == 0) {
          state = State.READ_COMMON_HEADER;
        }
        return;

      case FRAME_ERROR:
        buffer.skipBytes(buffer.readableBytes());
        return;

      default:
        throw new Error("Shouldn't reach here.");
    }
  }