/** * Handles post-mortem of writes that have failed. * * @param cause the cause of the failure. * @param propagateErrorIfRequired if {@code true} and {@code cause} is not an instance of {@link * Exception}, the error is propagated through the netty pipeline. */ private void handleChannelWriteFailure(Throwable cause, boolean propagateErrorIfRequired) { long writeFailureProcessingStartTime = System.currentTimeMillis(); try { nettyMetrics.channelWriteError.inc(); Exception exception; if (!(cause instanceof Exception)) { logger.warn("Encountered a throwable on channel write failure", cause); exception = new IllegalStateException("Encountered a Throwable - " + cause.getMessage()); if (propagateErrorIfRequired) { // we can't ignore throwables - so we let Netty deal with it. ctx.fireExceptionCaught(cause); nettyMetrics.throwableCount.inc(); } } else { exception = (Exception) cause; } onResponseComplete(exception); logger.trace("Cleaning up remaining chunks on write failure"); Chunk chunk = chunksAwaitingCallback.poll(); while (chunk != null) { chunk.resolveChunk(exception); chunk = chunksAwaitingCallback.poll(); } chunk = chunksToWrite.poll(); while (chunk != null) { chunksToWriteCount.decrementAndGet(); chunk.resolveChunk(exception); chunk = chunksToWrite.poll(); } } finally { nettyMetrics.channelWriteFailureProcessingTimeInMs.update( System.currentTimeMillis() - writeFailureProcessingStartTime); } }
/** Is called when a write timeout was detected */ protected void writeTimedOut(ChannelHandlerContext ctx) throws Exception { if (!closed) { ctx.fireExceptionCaught(WriteTimeoutException.INSTANCE); ctx.close(); closed = true; } }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof IOException) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( logIdent(ctx, endpoint) + "Connection reset by peer: " + cause.getMessage(), cause); } else { LOGGER.info(logIdent(ctx, endpoint) + "Connection reset by peer: " + cause.getMessage()); } handleOutstandingOperations(ctx); } else if (cause instanceof DecoderException && cause.getCause() instanceof SSLHandshakeException) { if (!connectFuture.isDone()) { connectFuture.setFailure(cause.getCause()); } else { // This should not be possible, since handshake is done before connecting. But just in case, // we // can trap and log an error that might slip through for one reason or another. LOGGER.warn( logIdent(ctx, endpoint) + "Caught SSL exception after being connected: " + cause.getMessage(), cause); } } else { LOGGER.warn( logIdent(ctx, endpoint) + "Caught unknown exception: " + cause.getMessage(), cause); ctx.fireExceptionCaught(cause); } }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof SpdyProtocolException) { issueSessionError(ctx, SpdySessionStatus.PROTOCOL_ERROR); } ctx.fireExceptionCaught(cause); }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (heartBeat != null) { heartBeat.cancel(true); heartBeat = null; } ctx.fireExceptionCaught(cause); }
@Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); if (failOnMissingResponse) { long missingResponses = requestResponseCounter.get(); if (missingResponses > 0) { ctx.fireExceptionCaught( new PrematureChannelClosureException( "channel gone inactive with " + missingResponses + " missing response(s)")); } } }
public boolean decode( ChannelHandlerContext ctx, final ByteBuf buf, InetSocketAddress recipient, final InetSocketAddress sender) { LOG.debug("Decoding of TomP2P starts now. Readable: {}.", buf.readableBytes()); try { final int readerBefore = buf.readerIndex(); // set the sender of this message for handling timeout final Attribute<InetSocketAddress> attributeInet = ctx.attr(INET_ADDRESS_KEY); attributeInet.set(sender); if (message == null && !headerDone) { headerDone = decodeHeader(buf, recipient, sender); if (headerDone) { // store the sender as an attribute final Attribute<PeerAddress> attributePeerAddress = ctx.attr(PEER_ADDRESS_KEY); attributePeerAddress.set(message.sender()); message.udp(ctx.channel() instanceof DatagramChannel); if (message.isFireAndForget() && message.isUdp()) { TimeoutFactory.removeTimeout(ctx); } } else { return false; } } final boolean donePayload = decodePayload(buf); decodeSignature(buf, readerBefore, donePayload); if (donePayload) { boolean isRelay = message.sender().isRelayed(); if (isRelay && !message.peerSocketAddresses().isEmpty()) { PeerAddress tmpSender = message.sender().changePeerSocketAddresses(message.peerSocketAddresses()); message.sender(tmpSender); } } // see https://github.com/netty/netty/issues/1976 buf.discardSomeReadBytes(); return donePayload; } catch (Exception e) { ctx.fireExceptionCaught(e); e.printStackTrace(); return true; } }
@Override public void run() { // Was not written yet so issue a write timeout // The promise itself will be failed with a ClosedChannelException once the close() was issued // See https://github.com/netty/netty/issues/2159 if (!promise.isDone()) { try { writeTimedOut(ctx); } catch (Throwable t) { ctx.fireExceptionCaught(t); } } removeWriteTimeoutTask(this); }
@Override public void channelRead0(ChannelHandlerContext ctx, Iterable<OFMessage> msgList) throws Exception { for (OFMessage ofm : msgList) { try { // Do the actual packet processing state.processOFMessage(ofm); } catch (Exception ex) { // We are the last handler in the stream, so run the // exception through the channel again by passing in // ctx.getChannel(). ctx.fireExceptionCaught(ex); } } }
@Override public void flush(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { ByteBuf in = ctx.outboundByteBuffer(); try { MessageBuf<Object> out = ctx.nextOutboundMessageBuffer(); ByteBuf payload = Unpooled.buffer(in.readableBytes()); payload.writeBytes(in); out.add(new SctpMessage(streamIdentifier, protocolIdentifier, payload)); in.discardReadBytes(); } catch (Throwable t) { ctx.fireExceptionCaught(new EncoderException(t)); } ctx.flush(promise); }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (ignoreException(cause)) { // It is safe to ignore the 'connection reset by peer' or // 'broken pipe' error after sending close_notify. if (logger.isDebugEnabled()) { logger.debug( "Swallowing a harmless 'connection reset by peer / broken pipe' error that occurred " + "while writing close_notify in response to the peer's close_notify", cause); } // Close the connection explicitly just in case the transport // did not close the connection automatically. if (ctx.channel().isActive()) { ctx.close(); } } else { ctx.fireExceptionCaught(cause); } }
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) { Preconditions.checkState( acceptMessage(msg), "Incorrect response type %s, %s.", msg.getClass().getCanonicalName(), msg); RPCProtoMessage response = (RPCProtoMessage) msg; Protocol.Status status = ((Protocol.Response) response.getMessage()).getStatus(); if (!Status.isOk(status)) { ctx.fireExceptionCaught( new IOException( String.format( "Failed to read block %d from %s with status %s.", mId, mAddress, status.toString()))); } mLock.lock(); try { Preconditions.checkState(mPacketReaderException == null); DataBuffer dataBuffer = response.getPayloadDataBuffer(); ByteBuf buf; if (dataBuffer == null) { buf = ctx.alloc().buffer(0, 0); } else { Preconditions.checkState(dataBuffer.getLength() > 0); assert dataBuffer.getNettyOutput() instanceof ByteBuf; buf = (ByteBuf) dataBuffer.getNettyOutput(); } mPackets.offer(buf); mNotEmptyOrFailed.signal(); if (tooManyPacketsPending()) { pause(); } } finally { mLock.unlock(); } }
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); }
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); nodeCheck.remove(ctx.channel().remoteAddress().toString()); // 删除缓存 ctx.close(); ctx.fireExceptionCaught(cause); }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.fireExceptionCaught(cause); }
@Override public void inboundBufferUpdated(final ChannelHandlerContext ctx) throws Exception { final ByteBuf in = ctx.inboundByteBuffer(); if (in.readableBytes() < 5) { return; } int packetLength = getEncryptedPacketLength(in); if (packetLength == -1) { // Bad data - discard the buffer and raise an exception. NotSslRecordException e = new NotSslRecordException("not an SSL/TLS record: " + ByteBufUtil.hexDump(in)); in.skipBytes(in.readableBytes()); ctx.fireExceptionCaught(e); setHandshakeFailure(e); return; } assert packetLength > 0; final ByteBuf out = ctx.nextInboundByteBuffer(); out.discardReadBytes(); boolean wrapLater = false; int bytesProduced = 0; try { loop: for (; ; ) { SSLEngineResult result = unwrap(engine, in, out); bytesProduced += result.bytesProduced(); switch (result.getStatus()) { case CLOSED: // notify about the CLOSED state of the SSLEngine. See #137 sslCloseFuture.setClosed(); break; case BUFFER_UNDERFLOW: break loop; } switch (result.getHandshakeStatus()) { case NEED_UNWRAP: break; case NEED_WRAP: wrapLater = true; break; case NEED_TASK: runDelegatedTasks(); break; case FINISHED: setHandshakeSuccess(); wrapLater = true; continue; case NOT_HANDSHAKING: break; default: throw new IllegalStateException( "Unknown handshake status: " + result.getHandshakeStatus()); } if (result.bytesConsumed() == 0 && result.bytesProduced() == 0) { break; } } if (wrapLater) { flush(ctx, ctx.newFuture()); } } catch (SSLException e) { setHandshakeFailure(e); throw e; } finally { if (bytesProduced > 0) { in.discardReadBytes(); ctx.fireInboundBufferUpdated(); } } }
@Override public void flush(final ChannelHandlerContext ctx, ChannelFuture future) throws Exception { final ByteBuf in = ctx.outboundByteBuffer(); final ByteBuf out = ctx.nextOutboundByteBuffer(); out.unsafe().discardSomeReadBytes(); // Do not encrypt the first write request if this handler is // created with startTLS flag turned on. if (startTls && !sentFirstMessage) { sentFirstMessage = true; out.writeBytes(in); ctx.flush(future); return; } if (ctx.executor() == ctx.channel().eventLoop()) { flushFutureNotifier.addFlushFuture(future, in.readableBytes()); } else { synchronized (flushFutureNotifier) { flushFutureNotifier.addFlushFuture(future, in.readableBytes()); } } boolean unwrapLater = false; int bytesConsumed = 0; try { for (; ; ) { SSLEngineResult result = wrap(engine, in, out); bytesConsumed += result.bytesConsumed(); if (result.getStatus() == Status.CLOSED) { // SSLEngine has been closed already. // Any further write attempts should be denied. if (in.readable()) { in.clear(); SSLException e = new SSLException("SSLEngine already closed"); future.setFailure(e); ctx.fireExceptionCaught(e); flush0(ctx, bytesConsumed, e); bytesConsumed = 0; } break; } else { switch (result.getHandshakeStatus()) { case NEED_WRAP: ctx.flush(); continue; case NEED_UNWRAP: if (ctx.inboundByteBuffer().readable()) { unwrapLater = true; } break; case NEED_TASK: runDelegatedTasks(); continue; case FINISHED: setHandshakeSuccess(); continue; case NOT_HANDSHAKING: break; default: throw new IllegalStateException( "Unknown handshake status: " + result.getHandshakeStatus()); } if (result.bytesConsumed() == 0 && result.bytesProduced() == 0) { break; } } } if (unwrapLater) { inboundBufferUpdated(ctx); } } catch (SSLException e) { setHandshakeFailure(e); throw e; } finally { in.unsafe().discardSomeReadBytes(); flush0(ctx, bytesConsumed); } }
@Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws SSLException { final int startOffset = in.readerIndex(); final int endOffset = in.writerIndex(); int offset = startOffset; int totalLength = 0; // If we calculated the length of the current SSL record before, use that information. if (packetLength > 0) { if (endOffset - startOffset < packetLength) { return; } else { offset += packetLength; totalLength = packetLength; packetLength = 0; } } boolean nonSslRecord = false; while (totalLength < OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) { final int readableBytes = endOffset - offset; if (readableBytes < 5) { break; } final int packetLength = getEncryptedPacketLength(in, offset); if (packetLength == -1) { nonSslRecord = true; break; } assert packetLength > 0; if (packetLength > readableBytes) { // wait until the whole packet can be read this.packetLength = packetLength; break; } int newTotalLength = totalLength + packetLength; if (newTotalLength > OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) { // Don't read too much. break; } // We have a whole packet. // Increment the offset to handle the next packet. offset += packetLength; totalLength = newTotalLength; } if (totalLength > 0) { boolean decoded = false; // The buffer contains one or more full SSL records. // Slice out the whole packet so unwrap will only be called with complete packets. // Also directly reset the packetLength. This is needed as unwrap(..) may trigger // decode(...) again via: // 1) unwrap(..) is called // 2) wrap(...) is called from within unwrap(...) // 3) wrap(...) calls unwrapLater(...) // 4) unwrapLater(...) calls decode(...) // // See https://github.com/netty/netty/issues/1534 in.skipBytes(totalLength); // If SSLEngine expects a heap buffer for unwrapping, do the conversion. if (in.isDirect() && wantsInboundHeapBuffer) { ByteBuf copy = ctx.alloc().heapBuffer(totalLength); try { copy.writeBytes(in, startOffset, totalLength); decoded = unwrap(ctx, copy, 0, totalLength); } finally { copy.release(); } } else { decoded = unwrap(ctx, in, startOffset, totalLength); } if (!firedChannelRead) { // Check first if firedChannelRead is not set yet as it may have been set in a // previous decode(...) call. firedChannelRead = decoded; } } if (nonSslRecord) { // Not an SSL/TLS packet NotSslRecordException e = new NotSslRecordException("not an SSL/TLS record: " + ByteBufUtil.hexDump(in)); in.skipBytes(in.readableBytes()); ctx.fireExceptionCaught(e); setHandshakeFailure(ctx, e); } }
private static void fireProtocolException(ChannelHandlerContext ctx, String message) { ctx.fireExceptionCaught(new SpdyProtocolException(message)); }
private static void fireInvalidFrameException(ChannelHandlerContext ctx) { ctx.fireExceptionCaught(INVALID_FRAME); }
@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."); } }
@Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws SSLException { // Keeps the list of the length of every SSL record in the input buffer. int[] recordLengths = null; int nRecords = 0; final int startOffset = in.readerIndex(); final int endOffset = in.writerIndex(); int offset = startOffset; // If we calculated the length of the current SSL record before, use that information. if (packetLength > 0) { if (endOffset - startOffset < packetLength) { return; } else { recordLengths = new int[4]; recordLengths[0] = packetLength; nRecords = 1; offset += packetLength; packetLength = 0; } } boolean nonSslRecord = false; for (; ; ) { final int readableBytes = endOffset - offset; if (readableBytes < 5) { break; } final int packetLength = getEncryptedPacketLength(in, offset); if (packetLength == -1) { nonSslRecord = true; break; } assert packetLength > 0; if (packetLength > readableBytes) { // wait until the whole packet can be read this.packetLength = packetLength; break; } // We have a whole packet. // Remember the length of the current packet. if (recordLengths == null) { recordLengths = new int[4]; } if (nRecords == recordLengths.length) { recordLengths = Arrays.copyOf(recordLengths, recordLengths.length << 1); } recordLengths[nRecords++] = packetLength; // Increment the offset to handle the next packet. offset += packetLength; } final int totalLength = offset - startOffset; if (totalLength > 0) { // The buffer contains one or more full SSL records. // Slice out the whole packet so unwrap will only be called with complete packets. // Also directly reset the packetLength. This is needed as unwrap(..) may trigger // decode(...) again via: // 1) unwrap(..) is called // 2) wrap(...) is called from within unwrap(...) // 3) wrap(...) calls unwrapLater(...) // 4) unwrapLater(...) calls decode(...) // // See https://github.com/netty/netty/issues/1534 in.skipBytes(totalLength); ByteBuffer buffer = in.nioBuffer(startOffset, totalLength); unwrapMultiple(ctx, buffer, totalLength, recordLengths, nRecords, out); } if (nonSslRecord) { // Not an SSL/TLS packet NotSslRecordException e = new NotSslRecordException("not an SSL/TLS record: " + ByteBufUtil.hexDump(in)); in.skipBytes(in.readableBytes()); ctx.fireExceptionCaught(e); setHandshakeFailure(e); } }