diff -Nru netty-4.0.33/all/pom.xml netty-4.0.34/all/pom.xml --- netty-4.0.33/all/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/all/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-all diff -Nru netty-4.0.33/buffer/pom.xml netty-4.0.34/buffer/pom.xml --- netty-4.0.33/buffer/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-buffer diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/AbstractByteBufAllocator.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/AbstractByteBufAllocator.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/AbstractByteBufAllocator.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/AbstractByteBufAllocator.java 2016-01-29 08:23:46.000000000 +0000 @@ -26,7 +26,7 @@ */ public abstract class AbstractByteBufAllocator implements ByteBufAllocator { private static final int DEFAULT_INITIAL_CAPACITY = 256; - private static final int DEFAULT_MAX_COMPONENTS = 16; + static final int DEFAULT_MAX_COMPONENTS = 16; protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) { ResourceLeak leak; @@ -48,6 +48,28 @@ return buf; } + protected static CompositeByteBuf toLeakAwareBuffer(CompositeByteBuf buf) { + ResourceLeak leak; + switch (ResourceLeakDetector.getLevel()) { + case SIMPLE: + leak = AbstractByteBuf.leakDetector.open(buf); + if (leak != null) { + buf = new SimpleLeakAwareCompositeByteBuf(buf, leak); + } + break; + case ADVANCED: + case PARANOID: + leak = AbstractByteBuf.leakDetector.open(buf); + if (leak != null) { + buf = new AdvancedLeakAwareCompositeByteBuf(buf, leak); + } + break; + default: + break; + } + return buf; + } + private final boolean directByDefault; private final ByteBuf emptyBuf; @@ -178,7 +200,7 @@ @Override public CompositeByteBuf compositeHeapBuffer(int maxNumComponents) { - return new CompositeByteBuf(this, false, maxNumComponents); + return toLeakAwareBuffer(new CompositeByteBuf(this, false, maxNumComponents)); } @Override @@ -188,7 +210,7 @@ @Override public CompositeByteBuf compositeDirectBuffer(int maxNumComponents) { - return new CompositeByteBuf(this, true, maxNumComponents); + return toLeakAwareBuffer(new CompositeByteBuf(this, true, maxNumComponents)); } private static void validate(int initialCapacity, int maxCapacity) { diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/AbstractReferenceCountedByteBuf.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/AbstractReferenceCountedByteBuf.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/AbstractReferenceCountedByteBuf.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/AbstractReferenceCountedByteBuf.java 2016-01-29 08:23:46.000000000 +0000 @@ -44,7 +44,7 @@ } @Override - public final int refCnt() { + public int refCnt() { return refCnt; } @@ -94,7 +94,7 @@ } @Override - public final boolean release() { + public boolean release() { for (;;) { int refCnt = this.refCnt; if (refCnt == 0) { @@ -112,7 +112,7 @@ } @Override - public final boolean release(int decrement) { + public boolean release(int decrement) { if (decrement <= 0) { throw new IllegalArgumentException("decrement: " + decrement + " (expected: > 0)"); } diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareByteBuf.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareByteBuf.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareByteBuf.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareByteBuf.java 2016-01-29 08:23:46.000000000 +0000 @@ -74,7 +74,7 @@ return deallocated; } - private void recordLeakNonRefCountingOperation() { + static void recordLeakNonRefCountingOperation(ResourceLeak leak) { if (!ACQUIRE_AND_RELEASE_ONLY) { leak.record(); } @@ -82,7 +82,7 @@ @Override public ByteBuf order(ByteOrder endianness) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); if (order() == endianness) { return this; } else { @@ -92,637 +92,637 @@ @Override public ByteBuf slice() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return new AdvancedLeakAwareByteBuf(super.slice(), leak); } @Override public ByteBuf slice(int index, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return new AdvancedLeakAwareByteBuf(super.slice(index, length), leak); } @Override public ByteBuf duplicate() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return new AdvancedLeakAwareByteBuf(super.duplicate(), leak); } @Override public ByteBuf readSlice(int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return new AdvancedLeakAwareByteBuf(super.readSlice(length), leak); } @Override public ByteBuf discardReadBytes() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.discardReadBytes(); } @Override public ByteBuf discardSomeReadBytes() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.discardSomeReadBytes(); } @Override public ByteBuf ensureWritable(int minWritableBytes) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.ensureWritable(minWritableBytes); } @Override public int ensureWritable(int minWritableBytes, boolean force) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.ensureWritable(minWritableBytes, force); } @Override public boolean getBoolean(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBoolean(index); } @Override public byte getByte(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getByte(index); } @Override public short getUnsignedByte(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getUnsignedByte(index); } @Override public short getShort(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getShort(index); } @Override public int getUnsignedShort(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getUnsignedShort(index); } @Override public int getMedium(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getMedium(index); } @Override public int getUnsignedMedium(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getUnsignedMedium(index); } @Override public int getInt(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getInt(index); } @Override public long getUnsignedInt(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getUnsignedInt(index); } @Override public long getLong(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getLong(index); } @Override public char getChar(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getChar(index); } @Override public float getFloat(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getFloat(index); } @Override public double getDouble(int index) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getDouble(index); } @Override public ByteBuf getBytes(int index, ByteBuf dst) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBytes(index, dst); } @Override public ByteBuf getBytes(int index, ByteBuf dst, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBytes(index, dst, length); } @Override public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBytes(index, dst, dstIndex, length); } @Override public ByteBuf getBytes(int index, byte[] dst) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBytes(index, dst); } @Override public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBytes(index, dst, dstIndex, length); } @Override public ByteBuf getBytes(int index, ByteBuffer dst) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBytes(index, dst); } @Override public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBytes(index, out, length); } @Override public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.getBytes(index, out, length); } @Override public ByteBuf setBoolean(int index, boolean value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBoolean(index, value); } @Override public ByteBuf setByte(int index, int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setByte(index, value); } @Override public ByteBuf setShort(int index, int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setShort(index, value); } @Override public ByteBuf setMedium(int index, int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setMedium(index, value); } @Override public ByteBuf setInt(int index, int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setInt(index, value); } @Override public ByteBuf setLong(int index, long value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setLong(index, value); } @Override public ByteBuf setChar(int index, int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setChar(index, value); } @Override public ByteBuf setFloat(int index, float value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setFloat(index, value); } @Override public ByteBuf setDouble(int index, double value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setDouble(index, value); } @Override public ByteBuf setBytes(int index, ByteBuf src) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBytes(index, src); } @Override public ByteBuf setBytes(int index, ByteBuf src, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBytes(index, src, length); } @Override public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBytes(index, src, srcIndex, length); } @Override public ByteBuf setBytes(int index, byte[] src) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBytes(index, src); } @Override public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBytes(index, src, srcIndex, length); } @Override public ByteBuf setBytes(int index, ByteBuffer src) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBytes(index, src); } @Override public int setBytes(int index, InputStream in, int length) throws IOException { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBytes(index, in, length); } @Override public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setBytes(index, in, length); } @Override public ByteBuf setZero(int index, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.setZero(index, length); } @Override public boolean readBoolean() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBoolean(); } @Override public byte readByte() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readByte(); } @Override public short readUnsignedByte() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readUnsignedByte(); } @Override public short readShort() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readShort(); } @Override public int readUnsignedShort() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readUnsignedShort(); } @Override public int readMedium() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readMedium(); } @Override public int readUnsignedMedium() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readUnsignedMedium(); } @Override public int readInt() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readInt(); } @Override public long readUnsignedInt() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readUnsignedInt(); } @Override public long readLong() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readLong(); } @Override public char readChar() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readChar(); } @Override public float readFloat() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readFloat(); } @Override public double readDouble() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readDouble(); } @Override public ByteBuf readBytes(int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(length); } @Override public ByteBuf readBytes(ByteBuf dst) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(dst); } @Override public ByteBuf readBytes(ByteBuf dst, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(dst, length); } @Override public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(dst, dstIndex, length); } @Override public ByteBuf readBytes(byte[] dst) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(dst); } @Override public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(dst, dstIndex, length); } @Override public ByteBuf readBytes(ByteBuffer dst) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(dst); } @Override public ByteBuf readBytes(OutputStream out, int length) throws IOException { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(out, length); } @Override public int readBytes(GatheringByteChannel out, int length) throws IOException { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.readBytes(out, length); } @Override public ByteBuf skipBytes(int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.skipBytes(length); } @Override public ByteBuf writeBoolean(boolean value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBoolean(value); } @Override public ByteBuf writeByte(int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeByte(value); } @Override public ByteBuf writeShort(int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeShort(value); } @Override public ByteBuf writeMedium(int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeMedium(value); } @Override public ByteBuf writeInt(int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeInt(value); } @Override public ByteBuf writeLong(long value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeLong(value); } @Override public ByteBuf writeChar(int value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeChar(value); } @Override public ByteBuf writeFloat(float value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeFloat(value); } @Override public ByteBuf writeDouble(double value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeDouble(value); } @Override public ByteBuf writeBytes(ByteBuf src) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBytes(src); } @Override public ByteBuf writeBytes(ByteBuf src, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBytes(src, length); } @Override public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBytes(src, srcIndex, length); } @Override public ByteBuf writeBytes(byte[] src) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBytes(src); } @Override public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBytes(src, srcIndex, length); } @Override public ByteBuf writeBytes(ByteBuffer src) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBytes(src); } @Override public int writeBytes(InputStream in, int length) throws IOException { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBytes(in, length); } @Override public int writeBytes(ScatteringByteChannel in, int length) throws IOException { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeBytes(in, length); } @Override public ByteBuf writeZero(int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.writeZero(length); } @Override public int indexOf(int fromIndex, int toIndex, byte value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.indexOf(fromIndex, toIndex, value); } @Override public int bytesBefore(byte value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.bytesBefore(value); } @Override public int bytesBefore(int length, byte value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.bytesBefore(length, value); } @Override public int bytesBefore(int index, int length, byte value) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.bytesBefore(index, length, value); } @Override public int forEachByte(ByteBufProcessor processor) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.forEachByte(processor); } @Override public int forEachByte(int index, int length, ByteBufProcessor processor) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.forEachByte(index, length, processor); } @Override public int forEachByteDesc(ByteBufProcessor processor) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.forEachByteDesc(processor); } @Override public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.forEachByteDesc(index, length, processor); } @Override public ByteBuf copy() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.copy(); } @Override public ByteBuf copy(int index, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.copy(index, length); } @Override public int nioBufferCount() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.nioBufferCount(); } @Override public ByteBuffer nioBuffer() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.nioBuffer(); } @Override public ByteBuffer nioBuffer(int index, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.nioBuffer(index, length); } @Override public ByteBuffer[] nioBuffers() { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.nioBuffers(); } @Override public ByteBuffer[] nioBuffers(int index, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.nioBuffers(index, length); } @Override public ByteBuffer internalNioBuffer(int index, int length) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.internalNioBuffer(index, length); } @Override public String toString(Charset charset) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.toString(charset); } @Override public String toString(int index, int length, Charset charset) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.toString(index, length, charset); } @@ -740,7 +740,7 @@ @Override public ByteBuf capacity(int newCapacity) { - recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(leak); return super.capacity(newCapacity); } } diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareCompositeByteBuf.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareCompositeByteBuf.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareCompositeByteBuf.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareCompositeByteBuf.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,806 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.buffer; + + +import io.netty.util.ResourceLeak; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.List; + +import static io.netty.buffer.AdvancedLeakAwareByteBuf.recordLeakNonRefCountingOperation; + +final class AdvancedLeakAwareCompositeByteBuf extends WrappedCompositeByteBuf { + + private final ResourceLeak leak; + + AdvancedLeakAwareCompositeByteBuf(CompositeByteBuf wrapped, ResourceLeak leak) { + super(wrapped); + this.leak = leak; + } + + @Override + public ByteBuf order(ByteOrder endianness) { + recordLeakNonRefCountingOperation(leak); + if (order() == endianness) { + return this; + } else { + return new AdvancedLeakAwareByteBuf(super.order(endianness), leak); + } + } + + @Override + public ByteBuf slice() { + recordLeakNonRefCountingOperation(leak); + return new AdvancedLeakAwareByteBuf(super.slice(), leak); + } + + @Override + public ByteBuf slice(int index, int length) { + recordLeakNonRefCountingOperation(leak); + return new AdvancedLeakAwareByteBuf(super.slice(index, length), leak); + } + + @Override + public ByteBuf duplicate() { + recordLeakNonRefCountingOperation(leak); + return new AdvancedLeakAwareByteBuf(super.duplicate(), leak); + } + + @Override + public ByteBuf readSlice(int length) { + recordLeakNonRefCountingOperation(leak); + return new AdvancedLeakAwareByteBuf(super.readSlice(length), leak); + } + + @Override + public CompositeByteBuf discardReadBytes() { + recordLeakNonRefCountingOperation(leak); + return super.discardReadBytes(); + } + + @Override + public CompositeByteBuf discardSomeReadBytes() { + recordLeakNonRefCountingOperation(leak); + return super.discardSomeReadBytes(); + } + + @Override + public CompositeByteBuf ensureWritable(int minWritableBytes) { + recordLeakNonRefCountingOperation(leak); + return super.ensureWritable(minWritableBytes); + } + + @Override + public int ensureWritable(int minWritableBytes, boolean force) { + recordLeakNonRefCountingOperation(leak); + return super.ensureWritable(minWritableBytes, force); + } + + @Override + public boolean getBoolean(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getBoolean(index); + } + + @Override + public byte getByte(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getByte(index); + } + + @Override + public short getUnsignedByte(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getUnsignedByte(index); + } + + @Override + public short getShort(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getShort(index); + } + + @Override + public int getUnsignedShort(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getUnsignedShort(index); + } + + @Override + public int getMedium(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getMedium(index); + } + + @Override + public int getUnsignedMedium(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getUnsignedMedium(index); + } + + @Override + public int getInt(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getInt(index); + } + + @Override + public long getUnsignedInt(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getUnsignedInt(index); + } + + @Override + public long getLong(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getLong(index); + } + + @Override + public char getChar(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getChar(index); + } + + @Override + public float getFloat(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getFloat(index); + } + + @Override + public double getDouble(int index) { + recordLeakNonRefCountingOperation(leak); + return super.getDouble(index); + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst) { + recordLeakNonRefCountingOperation(leak); + return super.getBytes(index, dst); + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst, int length) { + recordLeakNonRefCountingOperation(leak); + return super.getBytes(index, dst, length); + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + recordLeakNonRefCountingOperation(leak); + return super.getBytes(index, dst, dstIndex, length); + } + + @Override + public CompositeByteBuf getBytes(int index, byte[] dst) { + recordLeakNonRefCountingOperation(leak); + return super.getBytes(index, dst); + } + + @Override + public CompositeByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + recordLeakNonRefCountingOperation(leak); + return super.getBytes(index, dst, dstIndex, length); + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuffer dst) { + recordLeakNonRefCountingOperation(leak); + return super.getBytes(index, dst); + } + + @Override + public CompositeByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + recordLeakNonRefCountingOperation(leak); + return super.getBytes(index, out, length); + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + recordLeakNonRefCountingOperation(leak); + return super.getBytes(index, out, length); + } + + @Override + public CompositeByteBuf setBoolean(int index, boolean value) { + recordLeakNonRefCountingOperation(leak); + return super.setBoolean(index, value); + } + + @Override + public CompositeByteBuf setByte(int index, int value) { + recordLeakNonRefCountingOperation(leak); + return super.setByte(index, value); + } + + @Override + public CompositeByteBuf setShort(int index, int value) { + recordLeakNonRefCountingOperation(leak); + return super.setShort(index, value); + } + + @Override + public CompositeByteBuf setMedium(int index, int value) { + recordLeakNonRefCountingOperation(leak); + return super.setMedium(index, value); + } + + @Override + public CompositeByteBuf setInt(int index, int value) { + recordLeakNonRefCountingOperation(leak); + return super.setInt(index, value); + } + + @Override + public CompositeByteBuf setLong(int index, long value) { + recordLeakNonRefCountingOperation(leak); + return super.setLong(index, value); + } + + @Override + public CompositeByteBuf setChar(int index, int value) { + recordLeakNonRefCountingOperation(leak); + return super.setChar(index, value); + } + + @Override + public CompositeByteBuf setFloat(int index, float value) { + recordLeakNonRefCountingOperation(leak); + return super.setFloat(index, value); + } + + @Override + public CompositeByteBuf setDouble(int index, double value) { + recordLeakNonRefCountingOperation(leak); + return super.setDouble(index, value); + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src) { + recordLeakNonRefCountingOperation(leak); + return super.setBytes(index, src); + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src, int length) { + recordLeakNonRefCountingOperation(leak); + return super.setBytes(index, src, length); + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + recordLeakNonRefCountingOperation(leak); + return super.setBytes(index, src, srcIndex, length); + } + + @Override + public CompositeByteBuf setBytes(int index, byte[] src) { + recordLeakNonRefCountingOperation(leak); + return super.setBytes(index, src); + } + + @Override + public CompositeByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + recordLeakNonRefCountingOperation(leak); + return super.setBytes(index, src, srcIndex, length); + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuffer src) { + recordLeakNonRefCountingOperation(leak); + return super.setBytes(index, src); + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + recordLeakNonRefCountingOperation(leak); + return super.setBytes(index, in, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + recordLeakNonRefCountingOperation(leak); + return super.setBytes(index, in, length); + } + + @Override + public CompositeByteBuf setZero(int index, int length) { + recordLeakNonRefCountingOperation(leak); + return super.setZero(index, length); + } + + @Override + public boolean readBoolean() { + recordLeakNonRefCountingOperation(leak); + return super.readBoolean(); + } + + @Override + public byte readByte() { + recordLeakNonRefCountingOperation(leak); + return super.readByte(); + } + + @Override + public short readUnsignedByte() { + recordLeakNonRefCountingOperation(leak); + return super.readUnsignedByte(); + } + + @Override + public short readShort() { + recordLeakNonRefCountingOperation(leak); + return super.readShort(); + } + + @Override + public int readUnsignedShort() { + recordLeakNonRefCountingOperation(leak); + return super.readUnsignedShort(); + } + + @Override + public int readMedium() { + recordLeakNonRefCountingOperation(leak); + return super.readMedium(); + } + + @Override + public int readUnsignedMedium() { + recordLeakNonRefCountingOperation(leak); + return super.readUnsignedMedium(); + } + + @Override + public int readInt() { + recordLeakNonRefCountingOperation(leak); + return super.readInt(); + } + + @Override + public long readUnsignedInt() { + recordLeakNonRefCountingOperation(leak); + return super.readUnsignedInt(); + } + + @Override + public long readLong() { + recordLeakNonRefCountingOperation(leak); + return super.readLong(); + } + + @Override + public char readChar() { + recordLeakNonRefCountingOperation(leak); + return super.readChar(); + } + + @Override + public float readFloat() { + recordLeakNonRefCountingOperation(leak); + return super.readFloat(); + } + + @Override + public double readDouble() { + recordLeakNonRefCountingOperation(leak); + return super.readDouble(); + } + + @Override + public ByteBuf readBytes(int length) { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(length); + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst) { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(dst); + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst, int length) { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(dst, length); + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(dst, dstIndex, length); + } + + @Override + public CompositeByteBuf readBytes(byte[] dst) { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(dst); + } + + @Override + public CompositeByteBuf readBytes(byte[] dst, int dstIndex, int length) { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(dst, dstIndex, length); + } + + @Override + public CompositeByteBuf readBytes(ByteBuffer dst) { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(dst); + } + + @Override + public CompositeByteBuf readBytes(OutputStream out, int length) throws IOException { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(out, length); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + recordLeakNonRefCountingOperation(leak); + return super.readBytes(out, length); + } + + @Override + public CompositeByteBuf skipBytes(int length) { + recordLeakNonRefCountingOperation(leak); + return super.skipBytes(length); + } + + @Override + public CompositeByteBuf writeBoolean(boolean value) { + recordLeakNonRefCountingOperation(leak); + return super.writeBoolean(value); + } + + @Override + public CompositeByteBuf writeByte(int value) { + recordLeakNonRefCountingOperation(leak); + return super.writeByte(value); + } + + @Override + public CompositeByteBuf writeShort(int value) { + recordLeakNonRefCountingOperation(leak); + return super.writeShort(value); + } + + @Override + public CompositeByteBuf writeMedium(int value) { + recordLeakNonRefCountingOperation(leak); + return super.writeMedium(value); + } + + @Override + public CompositeByteBuf writeInt(int value) { + recordLeakNonRefCountingOperation(leak); + return super.writeInt(value); + } + + @Override + public CompositeByteBuf writeLong(long value) { + recordLeakNonRefCountingOperation(leak); + return super.writeLong(value); + } + + @Override + public CompositeByteBuf writeChar(int value) { + recordLeakNonRefCountingOperation(leak); + return super.writeChar(value); + } + + @Override + public CompositeByteBuf writeFloat(float value) { + recordLeakNonRefCountingOperation(leak); + return super.writeFloat(value); + } + + @Override + public CompositeByteBuf writeDouble(double value) { + recordLeakNonRefCountingOperation(leak); + return super.writeDouble(value); + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src) { + recordLeakNonRefCountingOperation(leak); + return super.writeBytes(src); + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src, int length) { + recordLeakNonRefCountingOperation(leak); + return super.writeBytes(src, length); + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + recordLeakNonRefCountingOperation(leak); + return super.writeBytes(src, srcIndex, length); + } + + @Override + public CompositeByteBuf writeBytes(byte[] src) { + recordLeakNonRefCountingOperation(leak); + return super.writeBytes(src); + } + + @Override + public CompositeByteBuf writeBytes(byte[] src, int srcIndex, int length) { + recordLeakNonRefCountingOperation(leak); + return super.writeBytes(src, srcIndex, length); + } + + @Override + public CompositeByteBuf writeBytes(ByteBuffer src) { + recordLeakNonRefCountingOperation(leak); + return super.writeBytes(src); + } + + @Override + public int writeBytes(InputStream in, int length) throws IOException { + recordLeakNonRefCountingOperation(leak); + return super.writeBytes(in, length); + } + + @Override + public int writeBytes(ScatteringByteChannel in, int length) throws IOException { + recordLeakNonRefCountingOperation(leak); + return super.writeBytes(in, length); + } + + @Override + public CompositeByteBuf writeZero(int length) { + recordLeakNonRefCountingOperation(leak); + return super.writeZero(length); + } + + @Override + public int indexOf(int fromIndex, int toIndex, byte value) { + recordLeakNonRefCountingOperation(leak); + return super.indexOf(fromIndex, toIndex, value); + } + + @Override + public int bytesBefore(byte value) { + recordLeakNonRefCountingOperation(leak); + return super.bytesBefore(value); + } + + @Override + public int bytesBefore(int length, byte value) { + recordLeakNonRefCountingOperation(leak); + return super.bytesBefore(length, value); + } + + @Override + public int bytesBefore(int index, int length, byte value) { + recordLeakNonRefCountingOperation(leak); + return super.bytesBefore(index, length, value); + } + + @Override + public int forEachByte(ByteBufProcessor processor) { + recordLeakNonRefCountingOperation(leak); + return super.forEachByte(processor); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + recordLeakNonRefCountingOperation(leak); + return super.forEachByte(index, length, processor); + } + + @Override + public int forEachByteDesc(ByteBufProcessor processor) { + recordLeakNonRefCountingOperation(leak); + return super.forEachByteDesc(processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + recordLeakNonRefCountingOperation(leak); + return super.forEachByteDesc(index, length, processor); + } + + @Override + public ByteBuf copy() { + recordLeakNonRefCountingOperation(leak); + return super.copy(); + } + + @Override + public ByteBuf copy(int index, int length) { + recordLeakNonRefCountingOperation(leak); + return super.copy(index, length); + } + + @Override + public int nioBufferCount() { + recordLeakNonRefCountingOperation(leak); + return super.nioBufferCount(); + } + + @Override + public ByteBuffer nioBuffer() { + recordLeakNonRefCountingOperation(leak); + return super.nioBuffer(); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + recordLeakNonRefCountingOperation(leak); + return super.nioBuffer(index, length); + } + + @Override + public ByteBuffer[] nioBuffers() { + recordLeakNonRefCountingOperation(leak); + return super.nioBuffers(); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + recordLeakNonRefCountingOperation(leak); + return super.nioBuffers(index, length); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + recordLeakNonRefCountingOperation(leak); + return super.internalNioBuffer(index, length); + } + + @Override + public String toString(Charset charset) { + recordLeakNonRefCountingOperation(leak); + return super.toString(charset); + } + + @Override + public String toString(int index, int length, Charset charset) { + recordLeakNonRefCountingOperation(leak); + return super.toString(index, length, charset); + } + + @Override + public CompositeByteBuf capacity(int newCapacity) { + recordLeakNonRefCountingOperation(leak); + return super.capacity(newCapacity); + } + + @Override + public CompositeByteBuf addComponent(ByteBuf buffer) { + recordLeakNonRefCountingOperation(leak); + return super.addComponent(buffer); + } + + @Override + public CompositeByteBuf addComponents(ByteBuf... buffers) { + recordLeakNonRefCountingOperation(leak); + return super.addComponents(buffers); + } + + @Override + public CompositeByteBuf addComponents(Iterable buffers) { + recordLeakNonRefCountingOperation(leak); + return super.addComponents(buffers); + } + + @Override + public CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) { + recordLeakNonRefCountingOperation(leak); + return super.addComponent(cIndex, buffer); + } + + @Override + public CompositeByteBuf addComponents(int cIndex, ByteBuf... buffers) { + recordLeakNonRefCountingOperation(leak); + return super.addComponents(cIndex, buffers); + } + + @Override + public CompositeByteBuf removeComponent(int cIndex) { + recordLeakNonRefCountingOperation(leak); + return super.removeComponent(cIndex); + } + + @Override + public CompositeByteBuf addComponents(int cIndex, Iterable buffers) { + recordLeakNonRefCountingOperation(leak); + return super.addComponents(cIndex, buffers); + } + + @Override + public CompositeByteBuf removeComponents(int cIndex, int numComponents) { + recordLeakNonRefCountingOperation(leak); + return super.removeComponents(cIndex, numComponents); + } + + @Override + public Iterator iterator() { + recordLeakNonRefCountingOperation(leak); + return super.iterator(); + } + + @Override + public List decompose(int offset, int length) { + recordLeakNonRefCountingOperation(leak); + return super.decompose(offset, length); + } + + @Override + public CompositeByteBuf consolidate() { + recordLeakNonRefCountingOperation(leak); + return super.consolidate(); + } + + @Override + public CompositeByteBuf discardReadComponents() { + recordLeakNonRefCountingOperation(leak); + return super.discardReadComponents(); + } + + @Override + public CompositeByteBuf consolidate(int cIndex, int numComponents) { + recordLeakNonRefCountingOperation(leak); + return super.consolidate(cIndex, numComponents); + } + + @Override + public CompositeByteBuf retain() { + leak.record(); + return super.retain(); + } + + @Override + public CompositeByteBuf retain(int increment) { + leak.record(); + return super.retain(increment); + } + + @Override + public boolean release() { + boolean deallocated = super.release(); + if (deallocated) { + leak.close(); + } else { + leak.record(); + } + return deallocated; + } + + @Override + public boolean release(int decrement) { + boolean deallocated = super.release(decrement); + if (deallocated) { + leak.close(); + } else { + leak.record(); + } + return deallocated; + } +} diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/ByteBuf.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/ByteBuf.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/ByteBuf.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/ByteBuf.java 2016-01-29 08:23:46.000000000 +0000 @@ -95,7 +95,7 @@ *

Writable bytes

* * This segment is a undefined space which needs to be filled. Any operation - * whose name ends with {@code write} will write the data at the current + * whose name starts with {@code write} will write the data at the current * {@link #writerIndex() writerIndex} and increase it by the number of written * bytes. If the argument of the write operation is also a {@link ByteBuf}, * and no source index is specified, the specified buffer's diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java 2016-01-29 08:23:46.000000000 +0000 @@ -19,7 +19,6 @@ import io.netty.util.Recycler; import io.netty.util.Recycler.Handle; import io.netty.util.concurrent.FastThreadLocal; -import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.SystemPropertyUtil; @@ -36,6 +35,11 @@ import java.nio.charset.CoderResult; import java.util.Locale; +import static io.netty.util.internal.MathUtil.isOutOfBounds; +import static io.netty.util.internal.ObjectUtil.checkNotNull; +import static io.netty.util.internal.StringUtil.NEWLINE; +import static io.netty.util.internal.StringUtil.isSurrogate; + /** * A collection of utility methods that is related with handling {@link ByteBuf}, * such as the generation of hex dump and swapping an integer's byte order. @@ -51,71 +55,11 @@ }; private static final int MAX_CHAR_BUFFER_SIZE; - private static final char[] HEXDUMP_TABLE = new char[256 * 4]; - private static final String NEWLINE = StringUtil.NEWLINE; - private static final String[] BYTE2HEX = new String[256]; - private static final String[] HEXPADDING = new String[16]; - private static final String[] BYTEPADDING = new String[16]; - private static final char[] BYTE2CHAR = new char[256]; - private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4]; + private static final int THREAD_LOCAL_BUFFER_SIZE; static final ByteBufAllocator DEFAULT_ALLOCATOR; - private static final int THREAD_LOCAL_BUFFER_SIZE; - static { - final char[] DIGITS = "0123456789abcdef".toCharArray(); - for (int i = 0; i < 256; i ++) { - HEXDUMP_TABLE[ i << 1 ] = DIGITS[i >>> 4 & 0x0F]; - HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F]; - } - - int i; - - // Generate the lookup table for byte-to-hex-dump conversion - for (i = 0; i < BYTE2HEX.length; i ++) { - BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i); - } - - // Generate the lookup table for hex dump paddings - for (i = 0; i < HEXPADDING.length; i ++) { - int padding = HEXPADDING.length - i; - StringBuilder buf = new StringBuilder(padding * 3); - for (int j = 0; j < padding; j ++) { - buf.append(" "); - } - HEXPADDING[i] = buf.toString(); - } - - // Generate the lookup table for byte dump paddings - for (i = 0; i < BYTEPADDING.length; i ++) { - int padding = BYTEPADDING.length - i; - StringBuilder buf = new StringBuilder(padding); - for (int j = 0; j < padding; j ++) { - buf.append(' '); - } - BYTEPADDING[i] = buf.toString(); - } - - // Generate the lookup table for byte-to-char conversion - for (i = 0; i < BYTE2CHAR.length; i ++) { - if (i <= 0x1f || i >= 0x7f) { - BYTE2CHAR[i] = '.'; - } else { - BYTE2CHAR[i] = (char) i; - } - } - - // Generate the lookup table for the start-offset header in each row (up to 64KiB). - for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i ++) { - StringBuilder buf = new StringBuilder(12); - buf.append(NEWLINE); - buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L)); - buf.setCharAt(buf.length() - 9, '|'); - buf.append('|'); - HEXDUMP_ROWPREFIXES[i] = buf.toString(); - } - String allocType = SystemPropertyUtil.get("io.netty.allocator.type", "unpooled").toLowerCase(Locale.US).trim(); ByteBufAllocator alloc; if ("unpooled".equals(allocType)) { @@ -151,28 +95,29 @@ * of the specified buffer's sub-region. */ public static String hexDump(ByteBuf buffer, int fromIndex, int length) { - if (length < 0) { - throw new IllegalArgumentException("length: " + length); - } - if (length == 0) { - return ""; - } - - int endIndex = fromIndex + length; - char[] buf = new char[length << 1]; + return HexUtil.hexDump(buffer, fromIndex, length); + } - int srcIdx = fromIndex; - int dstIdx = 0; - for (; srcIdx < endIndex; srcIdx ++, dstIdx += 2) { - System.arraycopy( - HEXDUMP_TABLE, buffer.getUnsignedByte(srcIdx) << 1, - buf, dstIdx, 2); - } + /** +<<<<<<< HEAD +======= + * Returns a hex dump + * of the specified byte array. + */ + public static String hexDump(byte[] array) { + return hexDump(array, 0, array.length); + } - return new String(buf); + /** + * Returns a hex dump + * of the specified byte array's sub-region. + */ + public static String hexDump(byte[] array, int fromIndex, int length) { + return HexUtil.hexDump(array, fromIndex, length); } /** +>>>>>>> e5386b0... Move Hex dump related util from ByteBufUtil to inner class * Calculates the hash code of the specified buffer. This method is * useful when implementing a new buffer type. */ @@ -435,6 +380,31 @@ } else if (c < 0x800) { buffer._setByte(writerIndex++, (byte) (0xc0 | (c >> 6))); buffer._setByte(writerIndex++, (byte) (0x80 | (c & 0x3f))); + } else if (isSurrogate(c)) { + if (!Character.isHighSurrogate(c)) { + throw new IllegalArgumentException("Invalid encoding. " + + "Expected high (leading) surrogate at index " + i + " but got " + c); + } + final char c2; + try { + // Surrogate Pair consumes 2 characters. Optimistically try to get the next character to avoid + // duplicate bounds checking with charAt. If an IndexOutOfBoundsException is thrown we will + // re-throw a more informative exception describing the problem. + c2 = seq.charAt(++i); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException("Underflow. " + + "Expected low (trailing) surrogate at index " + i + " but no more characters found.", e); + } + if (!Character.isLowSurrogate(c2)) { + throw new IllegalArgumentException("Invalid encoding. " + + "Expected low (trailing) surrogate at index " + i + " but got " + c2); + } + int codePoint = Character.toCodePoint(c, c2); + // See http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. + buffer._setByte(writerIndex++, (byte) (0xf0 | (codePoint >> 18))); + buffer._setByte(writerIndex++, (byte) (0x80 | ((codePoint >> 12) & 0x3f))); + buffer._setByte(writerIndex++, (byte) (0x80 | ((codePoint >> 6) & 0x3f))); + buffer._setByte(writerIndex++, (byte) (0x80 | (codePoint & 0x3f))); } else { buffer._setByte(writerIndex++, (byte) (0xe0 | (c >> 12))); buffer._setByte(writerIndex++, (byte) (0x80 | ((c >> 6) & 0x3f))); @@ -555,7 +525,7 @@ try { buffer.writeBytes(src, readerIndex, len); // Use internalNioBuffer(...) to reduce object creation. - decodeString(decoder, buffer.internalNioBuffer(readerIndex, len), dst); + decodeString(decoder, buffer.internalNioBuffer(0, len), dst); } finally { // Release the temporary buffer again. buffer.release(); @@ -591,14 +561,7 @@ * starting at the given {@code offset} using the given {@code length}. */ public static String prettyHexDump(ByteBuf buffer, int offset, int length) { - if (length == 0) { - return StringUtil.EMPTY_STRING; - } else { - int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4; - StringBuilder buf = new StringBuilder(rows * 80); - appendPrettyHexDump(buf, buffer, offset, length); - return buf.toString(); - } + return HexUtil.prettyHexDump(buffer, offset, length); } /** @@ -615,79 +578,201 @@ * the given {@code length}. */ public static void appendPrettyHexDump(StringBuilder dump, ByteBuf buf, int offset, int length) { - if (offset < 0 || length > ObjectUtil.checkNotNull(buf, "buf").capacity() - offset) { - throw new IndexOutOfBoundsException( - "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length - + ") <= " + "buf.capacity(" + buf.capacity() + ')'); - } - if (length == 0) { - return; - } - dump.append( - " +-------------------------------------------------+" + - NEWLINE + " | 0 1 2 3 4 5 6 7 8 9 a b c d e f |" + - NEWLINE + "+--------+-------------------------------------------------+----------------+"); + HexUtil.appendPrettyHexDump(dump, buf, offset, length); + } + + /* Separate class so that the expensive static initialization is only done when needed */ + private static final class HexUtil { - final int startIndex = offset; - final int fullRows = length >>> 4; - final int remainder = length & 0xF; + private static final char[] BYTE2CHAR = new char[256]; + private static final char[] HEXDUMP_TABLE = new char[256 * 4]; + private static final String[] HEXPADDING = new String[16]; + private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4]; + private static final String[] BYTE2HEX = new String[256]; + private static final String[] BYTEPADDING = new String[16]; + + static { + final char[] DIGITS = "0123456789abcdef".toCharArray(); + for (int i = 0; i < 256; i ++) { + HEXDUMP_TABLE[ i << 1 ] = DIGITS[i >>> 4 & 0x0F]; + HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F]; + } + + int i; + + // Generate the lookup table for hex dump paddings + for (i = 0; i < HEXPADDING.length; i ++) { + int padding = HEXPADDING.length - i; + StringBuilder buf = new StringBuilder(padding * 3); + for (int j = 0; j < padding; j ++) { + buf.append(" "); + } + HEXPADDING[i] = buf.toString(); + } - // Dump the rows which have 16 bytes. - for (int row = 0; row < fullRows; row ++) { - int rowStartIndex = (row << 4) + startIndex; + // Generate the lookup table for the start-offset header in each row (up to 64KiB). + for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i ++) { + StringBuilder buf = new StringBuilder(12); + buf.append(NEWLINE); + buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L)); + buf.setCharAt(buf.length() - 9, '|'); + buf.append('|'); + HEXDUMP_ROWPREFIXES[i] = buf.toString(); + } + + // Generate the lookup table for byte-to-hex-dump conversion + for (i = 0; i < BYTE2HEX.length; i ++) { + BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i); + } + + // Generate the lookup table for byte dump paddings + for (i = 0; i < BYTEPADDING.length; i ++) { + int padding = BYTEPADDING.length - i; + StringBuilder buf = new StringBuilder(padding); + for (int j = 0; j < padding; j ++) { + buf.append(' '); + } + BYTEPADDING[i] = buf.toString(); + } - // Per-row prefix. - appendHexDumpRowPrefix(dump, row, rowStartIndex); + // Generate the lookup table for byte-to-char conversion + for (i = 0; i < BYTE2CHAR.length; i ++) { + if (i <= 0x1f || i >= 0x7f) { + BYTE2CHAR[i] = '.'; + } else { + BYTE2CHAR[i] = (char) i; + } + } + } - // Hex dump - int rowEndIndex = rowStartIndex + 16; - for (int j = rowStartIndex; j < rowEndIndex; j ++) { - dump.append(BYTE2HEX[buf.getUnsignedByte(j)]); + private static String hexDump(ByteBuf buffer, int fromIndex, int length) { + if (length < 0) { + throw new IllegalArgumentException("length: " + length); + } + if (length == 0) { + return ""; } - dump.append(" |"); - // ASCII dump - for (int j = rowStartIndex; j < rowEndIndex; j ++) { - dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]); + int endIndex = fromIndex + length; + char[] buf = new char[length << 1]; + + int srcIdx = fromIndex; + int dstIdx = 0; + for (; srcIdx < endIndex; srcIdx ++, dstIdx += 2) { + System.arraycopy( + HEXDUMP_TABLE, buffer.getUnsignedByte(srcIdx) << 1, + buf, dstIdx, 2); } - dump.append('|'); + + return new String(buf); } - // Dump the last row which has less than 16 bytes. - if (remainder != 0) { - int rowStartIndex = (fullRows << 4) + startIndex; - appendHexDumpRowPrefix(dump, fullRows, rowStartIndex); + private static String hexDump(byte[] array, int fromIndex, int length) { + if (length < 0) { + throw new IllegalArgumentException("length: " + length); + } + if (length == 0) { + return ""; + } + + int endIndex = fromIndex + length; + char[] buf = new char[length << 1]; - // Hex dump - int rowEndIndex = rowStartIndex + remainder; - for (int j = rowStartIndex; j < rowEndIndex; j ++) { - dump.append(BYTE2HEX[buf.getUnsignedByte(j)]); + int srcIdx = fromIndex; + int dstIdx = 0; + for (; srcIdx < endIndex; srcIdx ++, dstIdx += 2) { + System.arraycopy( + HEXDUMP_TABLE, (array[srcIdx] & 0xFF) << 1, + buf, dstIdx, 2); } - dump.append(HEXPADDING[remainder]); - dump.append(" |"); - // Ascii dump - for (int j = rowStartIndex; j < rowEndIndex; j ++) { - dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]); + return new String(buf); + } + + private static String prettyHexDump(ByteBuf buffer, int offset, int length) { + if (length == 0) { + return StringUtil.EMPTY_STRING; + } else { + int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4; + StringBuilder buf = new StringBuilder(rows * 80); + appendPrettyHexDump(buf, buffer, offset, length); + return buf.toString(); } - dump.append(BYTEPADDING[remainder]); - dump.append('|'); } - dump.append(NEWLINE + "+--------+-------------------------------------------------+----------------+"); - } + private static void appendPrettyHexDump(StringBuilder dump, ByteBuf buf, int offset, int length) { + if (isOutOfBounds(offset, length, buf.capacity())) { + throw new IndexOutOfBoundsException( + "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length + + ") <= " + "buf.capacity(" + buf.capacity() + ')'); + } + if (length == 0) { + return; + } + dump.append( + " +-------------------------------------------------+" + + NEWLINE + " | 0 1 2 3 4 5 6 7 8 9 a b c d e f |" + + NEWLINE + "+--------+-------------------------------------------------+----------------+"); + + final int startIndex = offset; + final int fullRows = length >>> 4; + final int remainder = length & 0xF; + + // Dump the rows which have 16 bytes. + for (int row = 0; row < fullRows; row ++) { + int rowStartIndex = (row << 4) + startIndex; + + // Per-row prefix. + appendHexDumpRowPrefix(dump, row, rowStartIndex); + + // Hex dump + int rowEndIndex = rowStartIndex + 16; + for (int j = rowStartIndex; j < rowEndIndex; j ++) { + dump.append(BYTE2HEX[buf.getUnsignedByte(j)]); + } + dump.append(" |"); - /** - * Appends the prefix of each hex dump row. Uses the look-up table for the buffer <= 64 KiB. - */ - private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) { - if (row < HEXDUMP_ROWPREFIXES.length) { - dump.append(HEXDUMP_ROWPREFIXES[row]); - } else { - dump.append(NEWLINE); - dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L)); - dump.setCharAt(dump.length() - 9, '|'); - dump.append('|'); + // ASCII dump + for (int j = rowStartIndex; j < rowEndIndex; j ++) { + dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]); + } + dump.append('|'); + } + + // Dump the last row which has less than 16 bytes. + if (remainder != 0) { + int rowStartIndex = (fullRows << 4) + startIndex; + appendHexDumpRowPrefix(dump, fullRows, rowStartIndex); + + // Hex dump + int rowEndIndex = rowStartIndex + remainder; + for (int j = rowStartIndex; j < rowEndIndex; j ++) { + dump.append(BYTE2HEX[buf.getUnsignedByte(j)]); + } + dump.append(HEXPADDING[remainder]); + dump.append(" |"); + + // Ascii dump + for (int j = rowStartIndex; j < rowEndIndex; j ++) { + dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]); + } + dump.append(BYTEPADDING[remainder]); + dump.append('|'); + } + + dump.append(NEWLINE + + "+--------+-------------------------------------------------+----------------+"); + } + + private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) { + if (row < HEXDUMP_ROWPREFIXES.length) { + dump.append(HEXDUMP_ROWPREFIXES[row]); + } else { + dump.append(NEWLINE); + dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L)); + dump.setCharAt(dump.length() - 9, '|'); + dump.append('|'); + } } } diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java 2016-01-29 08:23:46.000000000 +0000 @@ -15,7 +15,6 @@ */ package io.netty.buffer; -import io.netty.util.ResourceLeak; import io.netty.util.internal.EmptyArrays; import java.io.IOException; @@ -44,10 +43,9 @@ private static final ByteBuffer EMPTY_NIO_BUFFER = Unpooled.EMPTY_BUFFER.nioBuffer(); private static final Iterator EMPTY_ITERATOR = Collections.emptyList().iterator(); - private final ResourceLeak leak; private final ByteBufAllocator alloc; private final boolean direct; - private final List components = new ArrayList(); + private final List components; private final int maxNumComponents; private boolean freed; @@ -60,7 +58,7 @@ this.alloc = alloc; this.direct = direct; this.maxNumComponents = maxNumComponents; - leak = leakDetector.open(this); + components = newList(maxNumComponents); } public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf... buffers) { @@ -76,11 +74,11 @@ this.alloc = alloc; this.direct = direct; this.maxNumComponents = maxNumComponents; + components = newList(maxNumComponents); addComponents0(0, buffers); consolidateIfNeeded(); setIndex(0, capacity()); - leak = leakDetector.open(this); } public CompositeByteBuf( @@ -97,10 +95,24 @@ this.alloc = alloc; this.direct = direct; this.maxNumComponents = maxNumComponents; + components = newList(maxNumComponents); + addComponents0(0, buffers); consolidateIfNeeded(); setIndex(0, capacity()); - leak = leakDetector.open(this); + } + + private static List newList(int maxNumComponents) { + return new ArrayList(Math.min(AbstractByteBufAllocator.DEFAULT_MAX_COMPONENTS, maxNumComponents)); + } + + // Special constructor used by WrappedCompositeByteBuf + CompositeByteBuf(ByteBufAllocator alloc) { + super(Integer.MAX_VALUE); + this.alloc = alloc; + direct = false; + maxNumComponents = 0; + components = Collections.emptyList(); } /** @@ -1624,10 +1636,6 @@ for (int i = 0; i < size; i++) { components.get(i).freeIfNecessary(); } - - if (leak != null) { - leak.close(); - } } @Override diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java 2016-01-29 08:23:46.000000000 +0000 @@ -280,6 +280,55 @@ return toLeakAwareBuffer(buf); } + /** + * Default number of heap areanas - System Property: io.netty.allocator.numHeapArenas - default 2 * cores + */ + public static int defaultNumHeapArena() { + return DEFAULT_NUM_HEAP_ARENA; + } + + /** + * Default numer of direct arenas - System Property: io.netty.allocator.numDirectArenas - default 2 * cores + */ + public static int defaultNumDirectArena() { + return DEFAULT_NUM_DIRECT_ARENA; + } + + /** + * Default buffer page size - System Property: io.netty.allocator.pageSize - default 8192 + */ + public static int defaultPageSize() { + return DEFAULT_PAGE_SIZE; + } + + /** + * Default maximum order - System Property: io.netty.allocator.maxOrder - default 11 + */ + public static int defaultMaxOrder() { + return DEFAULT_MAX_ORDER; + } + + /** + * Default tiny cache size - System Property: io.netty.allocator.tinyCacheSize - default 512 + */ + public static int defaultTinyCacheSize() { + return DEFAULT_TINY_CACHE_SIZE; + } + + /** + * Default small cache size - System Property: io.netty.allocator.smallCacheSize - default 256 + */ + public static int defaultSmallCacheSize() { + return DEFAULT_SMALL_CACHE_SIZE; + } + + /** + * Default normal cache size - System Property: io.netty.allocator.normalCacheSize - default 64 + */ + public static int defaultNormalCacheSize() { + return DEFAULT_NORMAL_CACHE_SIZE; + } + @Override public boolean isDirectBufferPooled() { return directArenas != null; diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/SimpleLeakAwareCompositeByteBuf.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/SimpleLeakAwareCompositeByteBuf.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/SimpleLeakAwareCompositeByteBuf.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/SimpleLeakAwareCompositeByteBuf.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,79 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.buffer; + + +import io.netty.util.ResourceLeak; + +import java.nio.ByteOrder; + +final class SimpleLeakAwareCompositeByteBuf extends WrappedCompositeByteBuf { + + private final ResourceLeak leak; + + SimpleLeakAwareCompositeByteBuf(CompositeByteBuf wrapped, ResourceLeak leak) { + super(wrapped); + this.leak = leak; + } + + @Override + public boolean release() { + boolean deallocated = super.release(); + if (deallocated) { + leak.close(); + } + return deallocated; + } + + @Override + public boolean release(int decrement) { + boolean deallocated = super.release(decrement); + if (deallocated) { + leak.close(); + } + return deallocated; + } + + @Override + public ByteBuf order(ByteOrder endianness) { + leak.record(); + if (order() == endianness) { + return this; + } else { + return new SimpleLeakAwareByteBuf(super.order(endianness), leak); + } + } + + @Override + public ByteBuf slice() { + return new SimpleLeakAwareByteBuf(super.slice(), leak); + } + + @Override + public ByteBuf slice(int index, int length) { + return new SimpleLeakAwareByteBuf(super.slice(index, length), leak); + } + + @Override + public ByteBuf duplicate() { + return new SimpleLeakAwareByteBuf(super.duplicate(), leak); + } + + @Override + public ByteBuf readSlice(int length) { + return new SimpleLeakAwareByteBuf(super.readSlice(length), leak); + } +} diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/Unpooled.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/Unpooled.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/Unpooled.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/Unpooled.java 2016-01-29 08:23:46.000000000 +0000 @@ -211,11 +211,15 @@ * Creates a new buffer which wraps the specified buffer's readable bytes. * A modification on the specified buffer's content will be visible to the * returned buffer. + * @param buffer The buffer to wrap. Reference count ownership of this variable is transfered to this method. + * @return The readable portion of the {@code buffer}, or an empty buffer if there is no readable portion. + * The caller is responsible for releasing this buffer. */ public static ByteBuf wrappedBuffer(ByteBuf buffer) { if (buffer.isReadable()) { return buffer.slice(); } else { + buffer.release(); return EMPTY_BUFFER; } } @@ -226,16 +230,18 @@ * content will be visible to the returned buffer. */ public static ByteBuf wrappedBuffer(byte[]... arrays) { - return wrappedBuffer(16, arrays); + return wrappedBuffer(AbstractByteBufAllocator.DEFAULT_MAX_COMPONENTS, arrays); } /** * Creates a new big-endian composite buffer which wraps the readable bytes of the * specified buffers without copying them. A modification on the content * of the specified buffers will be visible to the returned buffer. + * @param buffers The buffers to wrap. Reference count ownership of all variables is transfered to this method. + * @return The readable portion of the {@code buffers}. The caller is responsible for releasing this buffer. */ public static ByteBuf wrappedBuffer(ByteBuf... buffers) { - return wrappedBuffer(16, buffers); + return wrappedBuffer(AbstractByteBufAllocator.DEFAULT_MAX_COMPONENTS, buffers); } /** @@ -244,7 +250,7 @@ * specified buffers will be visible to the returned buffer. */ public static ByteBuf wrappedBuffer(ByteBuffer... buffers) { - return wrappedBuffer(16, buffers); + return wrappedBuffer(AbstractByteBufAllocator.DEFAULT_MAX_COMPONENTS, buffers); } /** @@ -285,20 +291,29 @@ * Creates a new big-endian composite buffer which wraps the readable bytes of the * specified buffers without copying them. A modification on the content * of the specified buffers will be visible to the returned buffer. + * @param maxNumComponents Advisement as to how many independent buffers are allowed to exist before + * consolidation occurs. + * @param buffers The buffers to wrap. Reference count ownership of all variables is transfered to this method. + * @return The readable portion of the {@code buffers}. The caller is responsible for releasing this buffer. */ public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers) { switch (buffers.length) { case 0: break; case 1: - if (buffers[0].isReadable()) { - return wrappedBuffer(buffers[0].order(BIG_ENDIAN)); + ByteBuf buffer = buffers[0]; + if (buffer.isReadable()) { + return wrappedBuffer(buffer.order(BIG_ENDIAN)); + } else { + buffer.release(); } break; default: for (ByteBuf b: buffers) { if (b.isReadable()) { return new CompositeByteBuf(ALLOC, false, maxNumComponents, buffers); + } else { + b.release(); } } } @@ -343,7 +358,7 @@ * Returns a new big-endian composite buffer with no components. */ public static CompositeByteBuf compositeBuffer() { - return compositeBuffer(16); + return compositeBuffer(AbstractByteBufAllocator.DEFAULT_MAX_COMPONENTS); } /** diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/UnsafeByteBufUtil.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/UnsafeByteBufUtil.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/UnsafeByteBufUtil.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/UnsafeByteBufUtil.java 2016-01-29 08:23:46.000000000 +0000 @@ -22,6 +22,7 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; import static io.netty.util.internal.MathUtil.isOutOfBounds; import static io.netty.util.internal.ObjectUtil.checkNotNull; @@ -315,15 +316,21 @@ } if (dst.isDirect()) { + if (dst.isReadOnly()) { + // We need to check if dst is ready-only so we not write something in it by using Unsafe. + throw new ReadOnlyBufferException(); + } // Copy to direct memory long dstAddress = PlatformDependent.directBufferAddress(dst); PlatformDependent.copyMemory(addr, dstAddress + dst.position(), bytesToCopy); - } else { + dst.position(dst.position() + bytesToCopy); + } else if (dst.hasArray()) { // Copy to array PlatformDependent.copyMemory(addr, dst.array(), dst.arrayOffset() + dst.position(), bytesToCopy); + dst.position(dst.position() + bytesToCopy); + } else { + dst.put(buf.nioBuffer()); } - - dst.position(dst.position() + bytesToCopy); } static void setBytes(AbstractByteBuf buf, long addr, int index, ByteBuf src, int srcIndex, int length) { @@ -363,11 +370,21 @@ // Copy from direct memory long srcAddress = PlatformDependent.directBufferAddress(src); PlatformDependent.copyMemory(srcAddress + src.position(), addr, src.remaining()); - } else { + src.position(src.position() + length); + } else if (src.hasArray()) { // Copy from array PlatformDependent.copyMemory(src.array(), src.arrayOffset() + src.position(), addr, length); + src.position(src.position() + length); + } else { + ByteBuf tmpBuf = buf.alloc().heapBuffer(length); + try { + byte[] tmp = tmpBuf.array(); + src.get(tmp, tmpBuf.arrayOffset(), length); // moves the src position too + PlatformDependent.copyMemory(tmp, 0, addr, length); + } finally { + tmpBuf.release(); + } } - src.position(src.position() + length); } static void getBytes(AbstractByteBuf buf, long addr, int index, OutputStream out, int length) throws IOException { diff -Nru netty-4.0.33/buffer/src/main/java/io/netty/buffer/WrappedCompositeByteBuf.java netty-4.0.34/buffer/src/main/java/io/netty/buffer/WrappedCompositeByteBuf.java --- netty-4.0.33/buffer/src/main/java/io/netty/buffer/WrappedCompositeByteBuf.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/buffer/src/main/java/io/netty/buffer/WrappedCompositeByteBuf.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,994 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.List; + +class WrappedCompositeByteBuf extends CompositeByteBuf { + + private final CompositeByteBuf wrapped; + + WrappedCompositeByteBuf(CompositeByteBuf wrapped) { + super(wrapped.alloc()); + this.wrapped = wrapped; + } + + @Override + public boolean release() { + return wrapped.release(); + } + + @Override + public boolean release(int decrement) { + return wrapped.release(decrement); + } + + @Override + public final int maxCapacity() { + return wrapped.maxCapacity(); + } + + @Override + public final int readerIndex() { + return wrapped.readerIndex(); + } + + @Override + public final int writerIndex() { + return wrapped.writerIndex(); + } + + @Override + public final boolean isReadable() { + return wrapped.isReadable(); + } + + @Override + public final boolean isReadable(int numBytes) { + return wrapped.isReadable(numBytes); + } + + @Override + public final boolean isWritable() { + return wrapped.isWritable(); + } + + @Override + public final boolean isWritable(int numBytes) { + return wrapped.isWritable(numBytes); + } + + @Override + public final int readableBytes() { + return wrapped.readableBytes(); + } + + @Override + public final int writableBytes() { + return wrapped.writableBytes(); + } + + @Override + public final int maxWritableBytes() { + return wrapped.maxWritableBytes(); + } + + @Override + public int ensureWritable(int minWritableBytes, boolean force) { + return wrapped.ensureWritable(minWritableBytes, force); + } + + @Override + public ByteBuf order(ByteOrder endianness) { + return wrapped.order(endianness); + } + + @Override + public boolean getBoolean(int index) { + return wrapped.getBoolean(index); + } + + @Override + public short getUnsignedByte(int index) { + return wrapped.getUnsignedByte(index); + } + + @Override + public short getShort(int index) { + return wrapped.getShort(index); + } + + @Override + public int getUnsignedShort(int index) { + return wrapped.getUnsignedShort(index); + } + + @Override + public int getUnsignedMedium(int index) { + return wrapped.getUnsignedMedium(index); + } + + @Override + public int getMedium(int index) { + return wrapped.getMedium(index); + } + + @Override + public int getInt(int index) { + return wrapped.getInt(index); + } + + @Override + public long getUnsignedInt(int index) { + return wrapped.getUnsignedInt(index); + } + + @Override + public long getLong(int index) { + return wrapped.getLong(index); + } + + @Override + public char getChar(int index) { + return wrapped.getChar(index); + } + + @Override + public float getFloat(int index) { + return wrapped.getFloat(index); + } + + @Override + public double getDouble(int index) { + return wrapped.getDouble(index); + } + + @Override + public byte readByte() { + return wrapped.readByte(); + } + + @Override + public boolean readBoolean() { + return wrapped.readBoolean(); + } + + @Override + public short readUnsignedByte() { + return wrapped.readUnsignedByte(); + } + + @Override + public short readShort() { + return wrapped.readShort(); + } + + @Override + public int readUnsignedShort() { + return wrapped.readUnsignedShort(); + } + + @Override + public int readMedium() { + return wrapped.readMedium(); + } + + @Override + public int readUnsignedMedium() { + return wrapped.readUnsignedMedium(); + } + + @Override + public int readInt() { + return wrapped.readInt(); + } + + @Override + public long readUnsignedInt() { + return wrapped.readUnsignedInt(); + } + + @Override + public long readLong() { + return wrapped.readLong(); + } + + @Override + public char readChar() { + return wrapped.readChar(); + } + + @Override + public float readFloat() { + return wrapped.readFloat(); + } + + @Override + public double readDouble() { + return wrapped.readDouble(); + } + + @Override + public ByteBuf readBytes(int length) { + return wrapped.readBytes(length); + } + + @Override + public ByteBuf slice() { + return wrapped.slice(); + } + + @Override + public ByteBuf slice(int index, int length) { + return wrapped.slice(index, length); + } + + @Override + public ByteBuffer nioBuffer() { + return wrapped.nioBuffer(); + } + + @Override + public String toString(Charset charset) { + return wrapped.toString(charset); + } + + @Override + public String toString(int index, int length, Charset charset) { + return wrapped.toString(index, length, charset); + } + + @Override + public int indexOf(int fromIndex, int toIndex, byte value) { + return wrapped.indexOf(fromIndex, toIndex, value); + } + + @Override + public int bytesBefore(byte value) { + return wrapped.bytesBefore(value); + } + + @Override + public int bytesBefore(int length, byte value) { + return wrapped.bytesBefore(length, value); + } + + @Override + public int bytesBefore(int index, int length, byte value) { + return wrapped.bytesBefore(index, length, value); + } + + @Override + public int forEachByte(ByteBufProcessor processor) { + return wrapped.forEachByte(processor); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + return wrapped.forEachByte(index, length, processor); + } + + @Override + public int forEachByteDesc(ByteBufProcessor processor) { + return wrapped.forEachByteDesc(processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + return wrapped.forEachByteDesc(index, length, processor); + } + + @Override + public final int hashCode() { + return wrapped.hashCode(); + } + + @Override + public final boolean equals(Object o) { + return wrapped.equals(o); + } + + @Override + public final int compareTo(ByteBuf that) { + return wrapped.compareTo(that); + } + + @Override + public final int refCnt() { + return wrapped.refCnt(); + } + + @Override + public ByteBuf duplicate() { + return wrapped.duplicate(); + } + + @Override + public ByteBuf readSlice(int length) { + return wrapped.readSlice(length); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + return wrapped.readBytes(out, length); + } + + @Override + public int writeBytes(InputStream in, int length) throws IOException { + return wrapped.writeBytes(in, length); + } + + @Override + public int writeBytes(ScatteringByteChannel in, int length) throws IOException { + return wrapped.writeBytes(in, length); + } + + @Override + public ByteBuf copy() { + return wrapped.copy(); + } + + @Override + public CompositeByteBuf addComponent(ByteBuf buffer) { + wrapped.addComponent(buffer); + return this; + } + + @Override + public CompositeByteBuf addComponents(ByteBuf... buffers) { + wrapped.addComponents(buffers); + return this; + } + + @Override + public CompositeByteBuf addComponents(Iterable buffers) { + wrapped.addComponents(buffers); + return this; + } + + @Override + public CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) { + wrapped.addComponent(cIndex, buffer); + return this; + } + + @Override + public CompositeByteBuf addComponents(int cIndex, ByteBuf... buffers) { + wrapped.addComponents(cIndex, buffers); + return this; + } + + @Override + public CompositeByteBuf addComponents(int cIndex, Iterable buffers) { + wrapped.addComponents(cIndex, buffers); + return this; + } + + @Override + public CompositeByteBuf removeComponent(int cIndex) { + wrapped.removeComponent(cIndex); + return this; + } + + @Override + public CompositeByteBuf removeComponents(int cIndex, int numComponents) { + wrapped.removeComponents(cIndex, numComponents); + return this; + } + + @Override + public Iterator iterator() { + return wrapped.iterator(); + } + + @Override + public List decompose(int offset, int length) { + return wrapped.decompose(offset, length); + } + + @Override + public final boolean isDirect() { + return wrapped.isDirect(); + } + + @Override + public final boolean hasArray() { + return wrapped.hasArray(); + } + + @Override + public final byte[] array() { + return wrapped.array(); + } + + @Override + public final int arrayOffset() { + return wrapped.arrayOffset(); + } + + @Override + public final boolean hasMemoryAddress() { + return wrapped.hasMemoryAddress(); + } + + @Override + public final long memoryAddress() { + return wrapped.memoryAddress(); + } + + @Override + public final int capacity() { + return wrapped.capacity(); + } + + @Override + public CompositeByteBuf capacity(int newCapacity) { + wrapped.capacity(newCapacity); + return this; + } + + @Override + public final ByteBufAllocator alloc() { + return wrapped.alloc(); + } + + @Override + public final ByteOrder order() { + return wrapped.order(); + } + + @Override + public final int numComponents() { + return wrapped.numComponents(); + } + + @Override + public final int maxNumComponents() { + return wrapped.maxNumComponents(); + } + + @Override + public final int toComponentIndex(int offset) { + return wrapped.toComponentIndex(offset); + } + + @Override + public final int toByteIndex(int cIndex) { + return wrapped.toByteIndex(cIndex); + } + + @Override + public byte getByte(int index) { + return wrapped.getByte(index); + } + + @Override + protected final byte _getByte(int index) { + return wrapped._getByte(index); + } + + @Override + protected final short _getShort(int index) { + return wrapped._getShort(index); + } + + @Override + protected final int _getUnsignedMedium(int index) { + return wrapped._getUnsignedMedium(index); + } + + @Override + protected final int _getInt(int index) { + return wrapped._getInt(index); + } + + @Override + protected final long _getLong(int index) { + return wrapped._getLong(index); + } + + @Override + public CompositeByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + wrapped.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuffer dst) { + wrapped.getBytes(index, dst); + return this; + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + wrapped.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return wrapped.getBytes(index, out, length); + } + + @Override + public CompositeByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + wrapped.getBytes(index, out, length); + return this; + } + + @Override + public CompositeByteBuf setByte(int index, int value) { + wrapped.setByte(index, value); + return this; + } + + @Override + protected final void _setByte(int index, int value) { + wrapped._setByte(index, value); + } + + @Override + public CompositeByteBuf setShort(int index, int value) { + wrapped.setShort(index, value); + return this; + } + + @Override + protected final void _setShort(int index, int value) { + wrapped._setShort(index, value); + } + + @Override + public CompositeByteBuf setMedium(int index, int value) { + wrapped.setMedium(index, value); + return this; + } + + @Override + protected final void _setMedium(int index, int value) { + wrapped._setMedium(index, value); + } + + @Override + public CompositeByteBuf setInt(int index, int value) { + wrapped.setInt(index, value); + return this; + } + + @Override + protected final void _setInt(int index, int value) { + wrapped._setInt(index, value); + } + + @Override + public CompositeByteBuf setLong(int index, long value) { + wrapped.setLong(index, value); + return this; + } + + @Override + protected final void _setLong(int index, long value) { + wrapped._setLong(index, value); + } + + @Override + public CompositeByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + wrapped.setBytes(index, src, srcIndex, length); + return this; + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuffer src) { + wrapped.setBytes(index, src); + return this; + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + wrapped.setBytes(index, src, srcIndex, length); + return this; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + return wrapped.setBytes(index, in, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + return wrapped.setBytes(index, in, length); + } + + @Override + public ByteBuf copy(int index, int length) { + return wrapped.copy(index, length); + } + + @Override + public final ByteBuf component(int cIndex) { + return wrapped.component(cIndex); + } + + @Override + public final ByteBuf componentAtOffset(int offset) { + return wrapped.componentAtOffset(offset); + } + + @Override + public final ByteBuf internalComponent(int cIndex) { + return wrapped.internalComponent(cIndex); + } + + @Override + public final ByteBuf internalComponentAtOffset(int offset) { + return wrapped.internalComponentAtOffset(offset); + } + + @Override + public int nioBufferCount() { + return wrapped.nioBufferCount(); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + return wrapped.internalNioBuffer(index, length); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + return wrapped.nioBuffer(index, length); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return wrapped.nioBuffers(index, length); + } + + @Override + public CompositeByteBuf consolidate() { + wrapped.consolidate(); + return this; + } + + @Override + public CompositeByteBuf consolidate(int cIndex, int numComponents) { + wrapped.consolidate(cIndex, numComponents); + return this; + } + + @Override + public CompositeByteBuf discardReadComponents() { + wrapped.discardReadComponents(); + return this; + } + + @Override + public CompositeByteBuf discardReadBytes() { + wrapped.discardReadBytes(); + return this; + } + + @Override + public final String toString() { + return wrapped.toString(); + } + + @Override + public final CompositeByteBuf readerIndex(int readerIndex) { + wrapped.readerIndex(readerIndex); + return this; + } + + @Override + public final CompositeByteBuf writerIndex(int writerIndex) { + wrapped.writerIndex(writerIndex); + return this; + } + + @Override + public final CompositeByteBuf setIndex(int readerIndex, int writerIndex) { + wrapped.setIndex(readerIndex, writerIndex); + return this; + } + + @Override + public final CompositeByteBuf clear() { + wrapped.clear(); + return this; + } + + @Override + public final CompositeByteBuf markReaderIndex() { + wrapped.markReaderIndex(); + return this; + } + + @Override + public final CompositeByteBuf resetReaderIndex() { + wrapped.resetReaderIndex(); + return this; + } + + @Override + public final CompositeByteBuf markWriterIndex() { + wrapped.markWriterIndex(); + return this; + } + + @Override + public final CompositeByteBuf resetWriterIndex() { + wrapped.resetWriterIndex(); + return this; + } + + @Override + public CompositeByteBuf ensureWritable(int minWritableBytes) { + wrapped.ensureWritable(minWritableBytes); + return this; + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst) { + wrapped.getBytes(index, dst); + return this; + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst, int length) { + wrapped.getBytes(index, dst, length); + return this; + } + + @Override + public CompositeByteBuf getBytes(int index, byte[] dst) { + wrapped.getBytes(index, dst); + return this; + } + + @Override + public CompositeByteBuf setBoolean(int index, boolean value) { + wrapped.setBoolean(index, value); + return this; + } + + @Override + public CompositeByteBuf setChar(int index, int value) { + wrapped.setChar(index, value); + return this; + } + + @Override + public CompositeByteBuf setFloat(int index, float value) { + wrapped.setFloat(index, value); + return this; + } + + @Override + public CompositeByteBuf setDouble(int index, double value) { + wrapped.setDouble(index, value); + return this; + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src) { + wrapped.setBytes(index, src); + return this; + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src, int length) { + wrapped.setBytes(index, src, length); + return this; + } + + @Override + public CompositeByteBuf setBytes(int index, byte[] src) { + wrapped.setBytes(index, src); + return this; + } + + @Override + public CompositeByteBuf setZero(int index, int length) { + wrapped.setZero(index, length); + return this; + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst) { + wrapped.readBytes(dst); + return this; + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst, int length) { + wrapped.readBytes(dst, length); + return this; + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + wrapped.readBytes(dst, dstIndex, length); + return this; + } + + @Override + public CompositeByteBuf readBytes(byte[] dst) { + wrapped.readBytes(dst); + return this; + } + + @Override + public CompositeByteBuf readBytes(byte[] dst, int dstIndex, int length) { + wrapped.readBytes(dst, dstIndex, length); + return this; + } + + @Override + public CompositeByteBuf readBytes(ByteBuffer dst) { + wrapped.readBytes(dst); + return this; + } + + @Override + public CompositeByteBuf readBytes(OutputStream out, int length) throws IOException { + wrapped.readBytes(out, length); + return this; + } + + @Override + public CompositeByteBuf skipBytes(int length) { + wrapped.skipBytes(length); + return this; + } + + @Override + public CompositeByteBuf writeBoolean(boolean value) { + wrapped.writeBoolean(value); + return this; + } + + @Override + public CompositeByteBuf writeByte(int value) { + wrapped.writeByte(value); + return this; + } + + @Override + public CompositeByteBuf writeShort(int value) { + wrapped.writeShort(value); + return this; + } + + @Override + public CompositeByteBuf writeMedium(int value) { + wrapped.writeMedium(value); + return this; + } + + @Override + public CompositeByteBuf writeInt(int value) { + wrapped.writeInt(value); + return this; + } + + @Override + public CompositeByteBuf writeLong(long value) { + wrapped.writeLong(value); + return this; + } + + @Override + public CompositeByteBuf writeChar(int value) { + wrapped.writeChar(value); + return this; + } + + @Override + public CompositeByteBuf writeFloat(float value) { + wrapped.writeFloat(value); + return this; + } + + @Override + public CompositeByteBuf writeDouble(double value) { + wrapped.writeDouble(value); + return this; + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src) { + wrapped.writeBytes(src); + return this; + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src, int length) { + wrapped.writeBytes(src, length); + return this; + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + wrapped.writeBytes(src, srcIndex, length); + return this; + } + + @Override + public CompositeByteBuf writeBytes(byte[] src) { + wrapped.writeBytes(src); + return this; + } + + @Override + public CompositeByteBuf writeBytes(byte[] src, int srcIndex, int length) { + wrapped.writeBytes(src, srcIndex, length); + return this; + } + + @Override + public CompositeByteBuf writeBytes(ByteBuffer src) { + wrapped.writeBytes(src); + return this; + } + + @Override + public CompositeByteBuf writeZero(int length) { + wrapped.writeZero(length); + return this; + } + + @Override + public CompositeByteBuf retain(int increment) { + wrapped.retain(increment); + return this; + } + + @Override + public CompositeByteBuf retain() { + wrapped.retain(); + return this; + } + + @Override + public ByteBuffer[] nioBuffers() { + return wrapped.nioBuffers(); + } + + @Override + public CompositeByteBuf discardSomeReadBytes() { + wrapped.discardSomeReadBytes(); + return this; + } + + @Override + public final void deallocate() { + wrapped.deallocate(); + } + + @Override + public final ByteBuf unwrap() { + return wrapped; + } +} diff -Nru netty-4.0.33/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java netty-4.0.34/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java --- netty-4.0.33/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -27,6 +27,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; import java.nio.channels.Channels; import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; @@ -2495,6 +2496,33 @@ assertFalse(nioBuffers[0].hasRemaining()); } + @Test + public void testGetReadOnlyDirectDst() { + testGetReadOnlyDst(true); + } + + @Test + public void testGetReadOnlyHeapDst() { + testGetReadOnlyDst(false); + } + + private void testGetReadOnlyDst(boolean direct) { + byte[] bytes = { 'a', 'b', 'c', 'd' }; + + ByteBuf buffer = releaseLater(newBuffer(bytes.length)); + buffer.writeBytes(bytes); + + ByteBuffer dst = direct ? ByteBuffer.allocateDirect(bytes.length) : ByteBuffer.allocate(bytes.length); + ByteBuffer readOnlyDst = dst.asReadOnlyBuffer(); + try { + buffer.getBytes(0, readOnlyDst); + fail(); + } catch (ReadOnlyBufferException e) { + // expected + } + assertEquals(0, readOnlyDst.position()); + } + private void testRefCnt0(final boolean parameter) throws Exception { for (int i = 0; i < 10; i++) { final CountDownLatch latch = new CountDownLatch(1); diff -Nru netty-4.0.33/buffer/src/test/java/io/netty/buffer/ByteBufUtilTest.java netty-4.0.34/buffer/src/test/java/io/netty/buffer/ByteBufUtilTest.java --- netty-4.0.33/buffer/src/test/java/io/netty/buffer/ByteBufUtilTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/test/java/io/netty/buffer/ByteBufUtilTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -60,6 +60,21 @@ } @Test + public void testWriteUtf8Surrogates() { + // leading surrogate + trailing surrogate + String surrogateString = new StringBuilder(2) + .append('\uD800') + .append('\uDC00') + .toString(); + ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer(16)); + buf.writeBytes(surrogateString.getBytes(CharsetUtil.UTF_8)); + ByteBuf buf2 = ReferenceCountUtil.releaseLater(Unpooled.buffer(16)); + ByteBufUtil.writeUtf8(buf2, surrogateString); + + Assert.assertEquals(buf, buf2); + } + + @Test public void testWriteUtf8Wrapped() { String usAscii = "Some UTF-8 like äÄ∏ŒŒ"; ByteBuf buf = Unpooled.unreleasableBuffer(ReferenceCountUtil.releaseLater(Unpooled.buffer(16))); @@ -91,4 +106,17 @@ Assert.assertEquals(text, ByteBufUtil.decodeString(buffer, 0, buffer.readableBytes(), charset)); buffer.release(); } + + @Test + public void testToStringDoesNotThrowIndexOutOfBounds() { + CompositeByteBuf buffer = Unpooled.compositeBuffer(); + try { + byte[] bytes = "1234".getBytes(CharsetUtil.UTF_8); + buffer.addComponent(Unpooled.buffer(bytes.length).writeBytes(bytes)); + buffer.addComponent(Unpooled.buffer(bytes.length).writeBytes(bytes)); + Assert.assertEquals("1234", buffer.toString(bytes.length, bytes.length, CharsetUtil.UTF_8)); + } finally { + buffer.release(); + } + } } diff -Nru netty-4.0.33/buffer/src/test/java/io/netty/buffer/SlicedByteBufTest.java netty-4.0.34/buffer/src/test/java/io/netty/buffer/SlicedByteBufTest.java --- netty-4.0.33/buffer/src/test/java/io/netty/buffer/SlicedByteBufTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/test/java/io/netty/buffer/SlicedByteBufTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -102,6 +102,18 @@ super.testWriteZeroAfterRelease(); } + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testGetReadOnlyDirectDst() { + super.testGetReadOnlyDirectDst(); + } + + @Test(expected = IndexOutOfBoundsException.class) + @Override + public void testGetReadOnlyHeapDst() { + super.testGetReadOnlyHeapDst(); + } + @Test @Override public void testLittleEndianWithExpand() { diff -Nru netty-4.0.33/buffer/src/test/java/io/netty/buffer/UnpooledTest.java netty-4.0.34/buffer/src/test/java/io/netty/buffer/UnpooledTest.java --- netty-4.0.33/buffer/src/test/java/io/netty/buffer/UnpooledTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/buffer/src/test/java/io/netty/buffer/UnpooledTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -284,6 +284,42 @@ } @Test + public void testSingleWrappedByteBufReleased() { + ByteBuf buf = buffer(12).writeByte(0); + ByteBuf wrapped = wrappedBuffer(buf); + assertTrue(wrapped.release()); + assertEquals(0, buf.refCnt()); + } + + @Test + public void testSingleUnReadableWrappedByteBufReleased() { + ByteBuf buf = buffer(12); + ByteBuf wrapped = wrappedBuffer(buf); + assertFalse(wrapped.release()); // EMPTY_BUFFER cannot be released + assertEquals(0, buf.refCnt()); + } + + @Test + public void testMultiByteBufReleased() { + ByteBuf buf1 = buffer(12).writeByte(0); + ByteBuf buf2 = buffer(12).writeByte(0); + ByteBuf wrapped = wrappedBuffer(16, buf1, buf2); + assertTrue(wrapped.release()); + assertEquals(0, buf1.refCnt()); + assertEquals(0, buf2.refCnt()); + } + + @Test + public void testMultiUnReadableByteBufReleased() { + ByteBuf buf1 = buffer(12); + ByteBuf buf2 = buffer(12); + ByteBuf wrapped = wrappedBuffer(16, buf1, buf2); + assertFalse(wrapped.release()); // EMPTY_BUFFER cannot be released + assertEquals(0, buf1.refCnt()); + assertEquals(0, buf2.refCnt()); + } + + @Test public void testCopiedBuffer() { assertEquals(16, copiedBuffer(ByteBuffer.allocateDirect(16)).capacity()); diff -Nru netty-4.0.33/buffer/src/test/java/io/netty/buffer/UnsafeByteBufUtilTest.java netty-4.0.34/buffer/src/test/java/io/netty/buffer/UnsafeByteBufUtilTest.java --- netty-4.0.33/buffer/src/test/java/io/netty/buffer/UnsafeByteBufUtilTest.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/buffer/src/test/java/io/netty/buffer/UnsafeByteBufUtilTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,48 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.buffer; + +import org.junit.Test; + +import java.nio.ByteBuffer; + +import static io.netty.util.internal.PlatformDependent.directBufferAddress; +import static org.junit.Assert.assertArrayEquals; + +public class UnsafeByteBufUtilTest { + + @Test + public void testSetBytesOnReadOnlyByteBuffer() throws Exception { + byte[] testData = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int length = testData.length; + + ByteBuffer readOnlyBuffer = ByteBuffer.wrap(testData).asReadOnlyBuffer(); + + UnpooledByteBufAllocator alloc = new UnpooledByteBufAllocator(true); + UnpooledDirectByteBuf targetBuffer = new UnpooledDirectByteBuf(alloc, length, length); + + try { + UnsafeByteBufUtil.setBytes(targetBuffer, directBufferAddress(targetBuffer.nioBuffer()), 0, readOnlyBuffer); + + byte[] check = new byte[length]; + targetBuffer.getBytes(0, check, 0, length); + + assertArrayEquals("The byte array's copy does not equal the original", testData, check); + } finally { + targetBuffer.release(); + } + } +} diff -Nru netty-4.0.33/codec/pom.xml netty-4.0.34/codec/pom.xml --- netty-4.0.33/codec/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-codec diff -Nru netty-4.0.33/codec/src/main/java/io/netty/handler/codec/base64/Base64.java netty-4.0.34/codec/src/main/java/io/netty/handler/codec/base64/Base64.java --- netty-4.0.33/codec/src/main/java/io/netty/handler/codec/base64/Base64.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/src/main/java/io/netty/handler/codec/base64/Base64.java 2016-01-29 08:23:46.000000000 +0000 @@ -20,7 +20,7 @@ package io.netty.handler.codec.base64; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import io.netty.buffer.ByteBufAllocator; /** * Utility class for {@link ByteBuf} that encodes and decodes to and from @@ -104,6 +104,11 @@ public static ByteBuf encode( ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect) { + return encode(src, off, len, breakLines, dialect, src.alloc()); + } + + public static ByteBuf encode( + ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect, ByteBufAllocator allocator) { if (src == null) { throw new NullPointerException("src"); @@ -113,7 +118,7 @@ } int len43 = len * 4 / 3; - ByteBuf dest = Unpooled.buffer( + ByteBuf dest = allocator.buffer( len43 + (len % 3 > 0 ? 4 : 0) + // Account for padding (breakLines ? len43 / MAX_LINE_LENGTH : 0)).order(src.order()); // New lines @@ -121,11 +126,15 @@ int e = 0; int len2 = len - 2; int lineLength = 0; - for (; d < len2; d += 3, e += 4) { + for (; d < len2; e += 4) { encode3to4(src, d + off, 3, dest, e, dialect); lineLength += 4; - if (breakLines && lineLength == MAX_LINE_LENGTH) { + d += 3; + + if (breakLines && lineLength == MAX_LINE_LENGTH + // Only add NEW_LINE if we not ended directly on the MAX_LINE_LENGTH + && d < len2) { dest.setByte(e + 4, NEW_LINE); e ++; lineLength = 0; @@ -206,6 +215,11 @@ public static ByteBuf decode( ByteBuf src, int off, int len, Base64Dialect dialect) { + return decode(src, off, len, dialect, src.alloc()); + } + + public static ByteBuf decode( + ByteBuf src, int off, int len, Base64Dialect dialect, ByteBufAllocator allocator) { if (src == null) { throw new NullPointerException("src"); @@ -217,7 +231,7 @@ byte[] DECODABET = decodabet(dialect); int len34 = len * 3 / 4; - ByteBuf dest = src.alloc().buffer(len34).order(src.order()); // Upper limit on size of output + ByteBuf dest = allocator.buffer(len34).order(src.order()); // Upper limit on size of output int outBuffPosn = 0; byte[] b4 = new byte[4]; diff -Nru netty-4.0.33/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java netty-4.0.34/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java --- netty-4.0.33/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java 2016-01-29 08:23:46.000000000 +0000 @@ -353,6 +353,15 @@ if (outSize > 0) { fireChannelRead(ctx, out, outSize); out.clear(); + + // Check if this handler was removed before continuing with decoding. + // If it was removed, it is not safe to continue to operate on the buffer. + // + // See: + // - https://github.com/netty/netty/issues/4635 + if (ctx.isRemoved()) { + break; + } outSize = 0; } diff -Nru netty-4.0.33/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibEncoder.java netty-4.0.34/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibEncoder.java --- netty-4.0.33/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibEncoder.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibEncoder.java 2016-01-29 08:23:46.000000000 +0000 @@ -22,6 +22,7 @@ import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromiseNotifier; import io.netty.util.concurrent.EventExecutor; +import io.netty.util.internal.OneTimeTask; import java.util.concurrent.TimeUnit; import java.util.zip.CRC32; @@ -163,7 +164,7 @@ return finishEncode(ctx, promise); } else { final ChannelPromise p = ctx.newPromise(); - executor.execute(new Runnable() { + executor.execute(new OneTimeTask() { @Override public void run() { ChannelFuture f = finishEncode(ctx(), p); @@ -259,7 +260,7 @@ if (!f.isDone()) { // Ensure the channel is closed even if the write operation completes in time. - ctx.executor().schedule(new Runnable() { + ctx.executor().schedule(new OneTimeTask() { @Override public void run() { ctx.close(promise); diff -Nru netty-4.0.33/codec/src/main/java/io/netty/handler/codec/compression/JZlibEncoder.java netty-4.0.34/codec/src/main/java/io/netty/handler/codec/compression/JZlibEncoder.java --- netty-4.0.33/codec/src/main/java/io/netty/handler/codec/compression/JZlibEncoder.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/src/main/java/io/netty/handler/codec/compression/JZlibEncoder.java 2016-01-29 08:23:46.000000000 +0000 @@ -26,6 +26,7 @@ import io.netty.channel.ChannelPromiseNotifier; import io.netty.util.concurrent.EventExecutor; import io.netty.util.internal.EmptyArrays; +import io.netty.util.internal.OneTimeTask; import java.util.concurrent.TimeUnit; @@ -252,7 +253,7 @@ return finishEncode(ctx, promise); } else { final ChannelPromise p = ctx.newPromise(); - executor.execute(new Runnable() { + executor.execute(new OneTimeTask() { @Override public void run() { ChannelFuture f = finishEncode(ctx(), p); @@ -351,7 +352,7 @@ if (!f.isDone()) { // Ensure the channel is closed even if the write operation completes in time. - ctx.executor().schedule(new Runnable() { + ctx.executor().schedule(new OneTimeTask() { @Override public void run() { ctx.close(promise); diff -Nru netty-4.0.33/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java netty-4.0.34/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java --- netty-4.0.33/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java 2016-01-29 08:23:46.000000000 +0000 @@ -229,7 +229,7 @@ * @param out The output buffer to copy to * @param length The length of the literal to copy */ - private static void encodeLiteral(ByteBuf in, ByteBuf out, int length) { + static void encodeLiteral(ByteBuf in, ByteBuf out, int length) { if (length < 61) { out.writeByte(length - 1 << 2); } else { @@ -396,7 +396,7 @@ * @param out The output buffer to write the literal to * @return The number of bytes appended to the output buffer, or -1 to indicate "try again later" */ - private static int decodeLiteral(byte tag, ByteBuf in, ByteBuf out) { + static int decodeLiteral(byte tag, ByteBuf in, ByteBuf out) { in.markReaderIndex(); int length; switch(tag >> 2 & 0x3F) { @@ -418,7 +418,7 @@ } length = ByteBufUtil.swapMedium(in.readUnsignedMedium()); break; - case 64: + case 63: if (in.readableBytes() < 4) { return NOT_ENOUGH_INPUT; } diff -Nru netty-4.0.33/codec/src/main/java/io/netty/handler/codec/compression/ZlibCodecFactory.java netty-4.0.34/codec/src/main/java/io/netty/handler/codec/compression/ZlibCodecFactory.java --- netty-4.0.33/codec/src/main/java/io/netty/handler/codec/compression/ZlibCodecFactory.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/src/main/java/io/netty/handler/codec/compression/ZlibCodecFactory.java 2016-01-29 08:23:46.000000000 +0000 @@ -33,7 +33,8 @@ private static final boolean noJdkZlibEncoder; static { - noJdkZlibDecoder = SystemPropertyUtil.getBoolean("io.netty.noJdkZlibDecoder", true); + noJdkZlibDecoder = SystemPropertyUtil.getBoolean("io.netty.noJdkZlibDecoder", + PlatformDependent.javaVersion() < 7); logger.debug("-Dio.netty.noJdkZlibDecoder: {}", noJdkZlibDecoder); noJdkZlibEncoder = SystemPropertyUtil.getBoolean("io.netty.noJdkZlibEncoder", false); diff -Nru netty-4.0.33/codec/src/test/java/io/netty/handler/codec/base64/Base64Test.java netty-4.0.34/codec/src/test/java/io/netty/handler/codec/base64/Base64Test.java --- netty-4.0.33/codec/src/test/java/io/netty/handler/codec/base64/Base64Test.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/codec/src/test/java/io/netty/handler/codec/base64/Base64Test.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,58 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.base64; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; +import org.junit.Test; + + +import static io.netty.buffer.Unpooled.copiedBuffer; +import static org.junit.Assert.assertEquals; + +public class Base64Test { + + @Test + public void testNotAddNewLineWhenEndOnLimit() { + ByteBuf src = copiedBuffer("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde", + CharsetUtil.US_ASCII); + ByteBuf expectedEncoded = + copiedBuffer("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFiY2Rl", + CharsetUtil.US_ASCII); + testEncode(src, expectedEncoded); + } + + @Test + public void testAddNewLine() { + ByteBuf src = copiedBuffer("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz12345678", + CharsetUtil.US_ASCII); + ByteBuf expectedEncoded = + copiedBuffer("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejEyMzQ1\nNjc4", + CharsetUtil.US_ASCII); + testEncode(src, expectedEncoded); + } + + private static void testEncode(ByteBuf src, ByteBuf expectedEncoded) { + ByteBuf encoded = Base64.encode(src, true, Base64Dialect.STANDARD); + try { + assertEquals(expectedEncoded, encoded); + } finally { + src.release(); + expectedEncoded.release(); + encoded.release(); + } + } +} diff -Nru netty-4.0.33/codec/src/test/java/io/netty/handler/codec/ByteToMessageDecoderTest.java netty-4.0.34/codec/src/test/java/io/netty/handler/codec/ByteToMessageDecoderTest.java --- netty-4.0.33/codec/src/test/java/io/netty/handler/codec/ByteToMessageDecoderTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/src/test/java/io/netty/handler/codec/ByteToMessageDecoderTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -21,13 +21,14 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.util.ReferenceCountUtil; -import org.junit.Assert; import org.junit.Test; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; +import static org.junit.Assert.*; + public class ByteToMessageDecoderTest { @Test @@ -37,7 +38,7 @@ @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - Assert.assertFalse(removed); + assertFalse(removed); in.readByte(); ctx.pipeline().remove(this); removed = true; @@ -47,20 +48,20 @@ ByteBuf buf = Unpooled.wrappedBuffer(new byte[] {'a', 'b', 'c'}); channel.writeInbound(buf.copy()); ByteBuf b = (ByteBuf) channel.readInbound(); - Assert.assertEquals(b, buf.skipBytes(1)); + assertEquals(b, buf.skipBytes(1)); b.release(); buf.release(); } @Test public void testRemoveItselfWriteBuffer() { - final ByteBuf buf = Unpooled.buffer().writeBytes(new byte[]{'a', 'b', 'c'}); + final ByteBuf buf = Unpooled.buffer().writeBytes(new byte[] {'a', 'b', 'c'}); EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { private boolean removed; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - Assert.assertFalse(removed); + assertFalse(removed); in.readByte(); ctx.pipeline().remove(this); @@ -71,8 +72,10 @@ }); channel.writeInbound(buf.copy()); + ByteBuf expected = Unpooled.wrappedBuffer(new byte[] {'b', 'c'}); ByteBuf b = (ByteBuf) channel.readInbound(); - Assert.assertEquals(b, Unpooled.wrappedBuffer(new byte[] { 'b', 'c'})); + assertEquals(expected, b); + expected.release(); buf.release(); b.release(); } @@ -83,20 +86,20 @@ */ @Test public void testInternalBufferClearReadAll() { - final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a'})); + final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[] {'a'})); EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { ByteBuf byteBuf = internalBuffer(); - Assert.assertEquals(1, byteBuf.refCnt()); + assertEquals(1, byteBuf.refCnt()); in.readByte(); // Removal from pipeline should clear internal buffer ctx.pipeline().remove(this); - Assert.assertEquals(0, byteBuf.refCnt()); + assertEquals(0, byteBuf.refCnt()); } }); - Assert.assertFalse(channel.writeInbound(buf)); - Assert.assertFalse(channel.finish()); + assertFalse(channel.writeInbound(buf)); + assertFalse(channel.finish()); } /** @@ -105,28 +108,32 @@ */ @Test public void testInternalBufferClearReadPartly() { - final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a', 'b'})); + final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[] {'a', 'b'})); EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { ByteBuf byteBuf = internalBuffer(); - Assert.assertEquals(1, byteBuf.refCnt()); + assertEquals(1, byteBuf.refCnt()); in.readByte(); // Removal from pipeline should clear internal buffer ctx.pipeline().remove(this); - Assert.assertEquals(0, byteBuf.refCnt()); + assertEquals(0, byteBuf.refCnt()); } }); - Assert.assertTrue(channel.writeInbound(buf)); - Assert.assertTrue(channel.finish()); - Assert.assertEquals(channel.readInbound(), Unpooled.wrappedBuffer(new byte[] {'b'})); - Assert.assertNull(channel.readInbound()); + assertTrue(channel.writeInbound(buf)); + assertTrue(channel.finish()); + ByteBuf expected = Unpooled.wrappedBuffer(new byte[] {'b'}); + ByteBuf b = (ByteBuf) channel.readInbound(); + assertEquals(expected, b); + assertNull(channel.readInbound()); + expected.release(); + b.release(); } @Test public void testFireChannelReadCompleteOnInactive() throws InterruptedException { final BlockingQueue queue = new LinkedBlockingDeque(); - final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a', 'b'})); + final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[] {'a', 'b'})); EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { @@ -153,11 +160,43 @@ } } }); - Assert.assertFalse(channel.writeInbound(buf)); + assertFalse(channel.writeInbound(buf)); channel.finish(); - Assert.assertEquals(1, (int) queue.take()); - Assert.assertEquals(2, (int) queue.take()); - Assert.assertEquals(3, (int) queue.take()); - Assert.assertTrue(queue.isEmpty()); + assertEquals(1, (int) queue.take()); + assertEquals(2, (int) queue.take()); + assertEquals(3, (int) queue.take()); + assertTrue(queue.isEmpty()); + } + + // See https://github.com/netty/netty/issues/4635 + @Test + public void testRemoveWhileInCallDecode() { + final Object upgradeMessage = new Object(); + final ByteToMessageDecoder decoder = new ByteToMessageDecoder() { + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + assertEquals('a', in.readByte()); + out.add(upgradeMessage); + } + }; + + EmbeddedChannel channel = new EmbeddedChannel(decoder, new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg == upgradeMessage) { + ctx.pipeline().remove(decoder); + return; + } + ctx.fireChannelRead(msg); + } + }); + + ByteBuf buf = Unpooled.wrappedBuffer(new byte[] { 'a', 'b', 'c' }); + assertTrue(channel.writeInbound(buf.copy())); + ByteBuf b = (ByteBuf) channel.readInbound(); + assertEquals(b, buf.skipBytes(1)); + assertFalse(channel.finish()); + buf.release(); + b.release(); } } diff -Nru netty-4.0.33/codec/src/test/java/io/netty/handler/codec/compression/SnappyTest.java netty-4.0.34/codec/src/test/java/io/netty/handler/codec/compression/SnappyTest.java --- netty-4.0.33/codec/src/test/java/io/netty/handler/codec/compression/SnappyTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec/src/test/java/io/netty/handler/codec/compression/SnappyTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -195,4 +195,32 @@ validateChecksum(maskChecksum(0xd6cb8b55), input); } + + @Test + public void testEncodeLiteralAndDecodeLiteral() { + int[] lengths = new int[] { + 0x11, // default + 0x100, // case 60 + 0x1000, // case 61 + 0x100000, // case 62 + 0x1000001 // case 63 + }; + for (int len : lengths) { + ByteBuf in = Unpooled.wrappedBuffer(new byte[len]); + ByteBuf encoded = Unpooled.buffer(10); + ByteBuf decoded = Unpooled.buffer(10); + ByteBuf expected = Unpooled.wrappedBuffer(new byte[len]); + try { + Snappy.encodeLiteral(in, encoded, len); + byte tag = encoded.readByte(); + Snappy.decodeLiteral(tag, encoded, decoded); + assertEquals("Encoded or decoded literal was incorrect", expected, decoded); + } finally { + in.release(); + encoded.release(); + decoded.release(); + expected.release(); + } + } + } } diff -Nru netty-4.0.33/codec-haproxy/pom.xml netty-4.0.34/codec-haproxy/pom.xml --- netty-4.0.33/codec-haproxy/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-haproxy/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-codec-haproxy diff -Nru netty-4.0.33/codec-http/pom.xml netty-4.0.34/codec-http/pom.xml --- netty-4.0.33/codec-http/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-codec-http diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieEncoder.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieEncoder.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieEncoder.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieEncoder.java 2016-01-29 08:23:46.000000000 +0000 @@ -15,16 +15,24 @@ */ package io.netty.handler.codec.http.cookie; -import static io.netty.handler.codec.http.cookie.CookieUtil.*; +import static io.netty.handler.codec.http.cookie.CookieUtil.add; +import static io.netty.handler.codec.http.cookie.CookieUtil.addQuoted; +import static io.netty.handler.codec.http.cookie.CookieUtil.stringBuilder; +import static io.netty.handler.codec.http.cookie.CookieUtil.stripTrailingSeparator; +import static io.netty.handler.codec.http.cookie.CookieUtil.stripTrailingSeparatorOrNull; import static io.netty.util.internal.ObjectUtil.checkNotNull; +import io.netty.handler.codec.http.HttpRequest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; import java.util.Iterator; - -import io.netty.handler.codec.http.HttpRequest; +import java.util.List; /** - * A RFC6265 compliant cookie encoder to be used client side, - * so only name=value pairs are sent. + * A RFC6265 compliant cookie encoder to be used client side, so + * only name=value pairs are sent. * * User-Agents are not supposed to interpret cookies, so, if present, {@link Cookie#rawValue()} will be used. * Otherwise, {@link Cookie#value()} will be used unquoted. @@ -42,13 +50,14 @@ public final class ClientCookieEncoder extends CookieEncoder { /** - * Strict encoder that validates that name and value chars are in the valid scope - * defined in RFC6265 + * Strict encoder that validates that name and value chars are in the valid scope and (for methods that accept + * multiple cookies) sorts cookies into order of decreasing path length, as specified in RFC6265. */ public static final ClientCookieEncoder STRICT = new ClientCookieEncoder(true); /** - * Lax instance that doesn't validate name and value + * Lax instance that doesn't validate name and value, and (for methods that accept multiple cookies) keeps + * cookies in the order in which they were given. */ public static final ClientCookieEncoder LAX = new ClientCookieEncoder(false); @@ -59,8 +68,10 @@ /** * Encodes the specified cookie into a Cookie header value. * - * @param name the cookie name - * @param value the cookie value + * @param name + * the cookie name + * @param value + * the cookie value * @return a Rfc6265 style Cookie header value */ public String encode(String name, String value) { @@ -70,7 +81,8 @@ /** * Encodes the specified cookie into a Cookie header value. * - * @param specified the cookie + * @param specified + * the cookie * @return a Rfc6265 style Cookie header value */ public String encode(Cookie cookie) { @@ -80,9 +92,36 @@ } /** + * Sort cookies into decreasing order of path length, breaking ties by sorting into increasing chronological + * order of creation time, as recommended by RFC 6265. + */ + private static final Comparator COOKIE_COMPARATOR = new Comparator() { + @Override + public int compare(Cookie c1, Cookie c2) { + String path1 = c1.path(); + String path2 = c2.path(); + // Cookies with unspecified path default to the path of the request. We don't + // know the request path here, but we assume that the length of an unspecified + // path is longer than any specified path (i.e. pathless cookies come first), + // because setting cookies with a path longer than the request path is of + // limited use. + int len1 = path1 == null ? Integer.MAX_VALUE : path1.length(); + int len2 = path2 == null ? Integer.MAX_VALUE : path2.length(); + int diff = len2 - len1; + if (diff != 0) { + return diff; + } + // Rely on Java's sort stability to retain creation order in cases where + // cookies have same path length. + return -1; + } + }; + + /** * Encodes the specified cookies into a single Cookie header value. * - * @param cookies some cookies + * @param cookies + * some cookies * @return a Rfc6265 style Cookie header value, null if no cookies are passed. */ public String encode(Cookie... cookies) { @@ -91,12 +130,51 @@ } StringBuilder buf = stringBuilder(); - for (Cookie c : cookies) { - if (c == null) { - break; + if (strict) { + if (cookies.length == 1) { + encode(buf, cookies[0]); + } else { + Cookie[] cookiesSorted = Arrays.copyOf(cookies, cookies.length); + Arrays.sort(cookiesSorted, COOKIE_COMPARATOR); + for (Cookie c : cookiesSorted) { + encode(buf, c); + } + } + } else { + for (Cookie c : cookies) { + encode(buf, c); } + } + return stripTrailingSeparatorOrNull(buf); + } - encode(buf, c); + /** + * Encodes the specified cookies into a single Cookie header value. + * + * @param cookies + * some cookies + * @return a Rfc6265 style Cookie header value, null if no cookies are passed. + */ + public String encode(Collection cookies) { + if (checkNotNull(cookies, "cookies").isEmpty()) { + return null; + } + + StringBuilder buf = stringBuilder(); + if (strict) { + if (cookies.size() == 1) { + encode(buf, cookies.iterator().next()); + } else { + Cookie[] cookiesSorted = cookies.toArray(new Cookie[cookies.size()]); + Arrays.sort(cookiesSorted, COOKIE_COMPARATOR); + for (Cookie c : cookiesSorted) { + encode(buf, c); + } + } + } else { + for (Cookie c : cookies) { + encode(buf, c); + } } return stripTrailingSeparatorOrNull(buf); } @@ -114,13 +192,26 @@ } StringBuilder buf = stringBuilder(); - while (cookiesIt.hasNext()) { - Cookie c = cookiesIt.next(); - if (c == null) { - break; + if (strict) { + Cookie firstCookie = cookiesIt.next(); + if (!cookiesIt.hasNext()) { + encode(buf, firstCookie); + } else { + List cookiesList = new ArrayList(); + cookiesList.add(firstCookie); + while (cookiesIt.hasNext()) { + cookiesList.add(cookiesIt.next()); + } + Cookie[] cookiesSorted = cookiesList.toArray(new Cookie[cookiesList.size()]); + Arrays.sort(cookiesSorted, COOKIE_COMPARATOR); + for (Cookie c : cookiesSorted) { + encode(buf, c); + } + } + } else { + while (cookiesIt.hasNext()) { + encode(buf, cookiesIt.next()); } - - encode(buf, c); } return stripTrailingSeparatorOrNull(buf); } diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/cookie/CookieEncoder.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/cookie/CookieEncoder.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/cookie/CookieEncoder.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/cookie/CookieEncoder.java 2016-01-29 08:23:46.000000000 +0000 @@ -24,7 +24,7 @@ */ public abstract class CookieEncoder { - private final boolean strict; + protected final boolean strict; protected CookieEncoder(boolean strict) { this.strict = strict; diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java 2016-01-29 08:23:46.000000000 +0000 @@ -15,9 +15,11 @@ */ package io.netty.handler.codec.http.cookie; -import static io.netty.handler.codec.http.cookie.CookieUtil.*; +import static io.netty.handler.codec.http.cookie.CookieUtil.add; +import static io.netty.handler.codec.http.cookie.CookieUtil.addQuoted; +import static io.netty.handler.codec.http.cookie.CookieUtil.stringBuilder; +import static io.netty.handler.codec.http.cookie.CookieUtil.stripTrailingSeparator; import static io.netty.util.internal.ObjectUtil.checkNotNull; - import io.netty.handler.codec.http.HttpHeaderDateFormat; import io.netty.handler.codec.http.HttpRequest; @@ -25,7 +27,10 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; /** * A RFC6265 compliant cookie encoder to be used server side, @@ -47,12 +52,15 @@ /** * Strict encoder that validates that name and value chars are in the valid scope - * defined in RFC6265 + * defined in RFC6265, and (for methods that accept multiple cookies) that only + * one cookie is encoded with any given name. (If multiple cookies have the same + * name, the last one is the one that is encoded.) */ public static final ServerCookieEncoder STRICT = new ServerCookieEncoder(true); /** - * Lax instance that doesn't validate name and value + * Lax instance that doesn't validate name and value, and that allows multiple + * cookies with the same name. */ public static final ServerCookieEncoder LAX = new ServerCookieEncoder(false); @@ -114,6 +122,26 @@ return stripTrailingSeparator(buf); } + /** Deduplicate a list of encoded cookies by keeping only the last instance with a given name. + * + * @param encoded The list of encoded cookies. + * @param nameToLastIndex A map from cookie name to index of last cookie instance. + * @return The encoded list with all but the last instance of a named cookie. + */ + private List dedup(List encoded, Map nameToLastIndex) { + boolean[] isLastInstance = new boolean[encoded.size()]; + for (int idx : nameToLastIndex.values()) { + isLastInstance[idx] = true; + } + List dedupd = new ArrayList(nameToLastIndex.size()); + for (int i = 0, n = encoded.size(); i < n; i++) { + if (isLastInstance[i]) { + dedupd.add(encoded.get(i)); + } + } + return dedupd; + } + /** * Batch encodes cookies into Set-Cookie header values. * @@ -126,13 +154,16 @@ } List encoded = new ArrayList(cookies.length); - for (Cookie c : cookies) { - if (c == null) { - break; - } + Map nameToIndex = strict && cookies.length > 1 ? new HashMap() : null; + boolean hasDupdName = false; + for (int i = 0; i < cookies.length; i++) { + Cookie c = cookies[i]; encoded.add(encode(c)); + if (nameToIndex != null) { + hasDupdName |= nameToIndex.put(c.name(), i) != null; + } } - return encoded; + return hasDupdName ? dedup(encoded, nameToIndex) : encoded; } /** @@ -147,13 +178,16 @@ } List encoded = new ArrayList(cookies.size()); + Map nameToIndex = strict && cookies.size() > 1 ? new HashMap() : null; + int i = 0; + boolean hasDupdName = false; for (Cookie c : cookies) { - if (c == null) { - break; - } encoded.add(encode(c)); + if (nameToIndex != null) { + hasDupdName |= nameToIndex.put(c.name(), i++) != null; + } } - return encoded; + return hasDupdName ? dedup(encoded, nameToIndex) : encoded; } /** @@ -163,17 +197,24 @@ * @return the corresponding bunch of Set-Cookie headers */ public List encode(Iterable cookies) { - if (!checkNotNull(cookies, "cookies").iterator().hasNext()) { + Iterator cookiesIt = checkNotNull(cookies, "cookies").iterator(); + if (!cookiesIt.hasNext()) { return Collections.emptyList(); } List encoded = new ArrayList(); - for (Cookie c : cookies) { - if (c == null) { - break; - } + Cookie firstCookie = cookiesIt.next(); + Map nameToIndex = strict && cookiesIt.hasNext() ? new HashMap() : null; + int i = 0; + encoded.add(encode(firstCookie)); + boolean hasDupdName = nameToIndex != null ? nameToIndex.put(firstCookie.name(), i++) != null : false; + while (cookiesIt.hasNext()) { + Cookie c = cookiesIt.next(); encoded.add(encode(c)); + if (nameToIndex != null) { + hasDupdName |= nameToIndex.put(c.name(), i++) != null; + } } - return encoded; + return hasDupdName ? dedup(encoded, nameToIndex) : encoded; } } diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java 2016-01-29 08:23:46.000000000 +0000 @@ -77,15 +77,14 @@ @Override public HttpHeaders set(HttpHeaders headers) { if (headers instanceof DefaultHttpHeaders) { - if (headers == this) { - throw new IllegalArgumentException("can't add to itself."); - } - clear(); - DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers; - HeaderEntry e = defaultHttpHeaders.head.after; - while (e != defaultHttpHeaders.head) { - add(e.key, e.value); - e = e.after; + if (headers != this) { + clear(); + DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers; + HeaderEntry e = defaultHttpHeaders.head.after; + while (e != defaultHttpHeaders.head) { + add(e.key, e.value); + e = e.after; + } } return this; } else { diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/FullHttpRequest.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/FullHttpRequest.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/FullHttpRequest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/FullHttpRequest.java 2016-01-29 08:23:46.000000000 +0000 @@ -16,7 +16,7 @@ package io.netty.handler.codec.http; /** - * Combinate the {@link HttpRequest} and {@link FullHttpMessage}, so the request is a complete HTTP + * Combine the {@link HttpRequest} and {@link FullHttpMessage}, so the request is a complete HTTP * request. */ public interface FullHttpRequest extends HttpRequest, FullHttpMessage { diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java 2016-01-29 08:23:46.000000000 +0000 @@ -1617,9 +1617,11 @@ if (headers == null) { throw new NullPointerException("headers"); } - clear(); - for (Map.Entry e: headers) { - add(e.getKey(), e.getValue()); + if (headers != this) { + clear(); + for (Map.Entry e : headers) { + add(e.getKey(), e.getValue()); + } } return this; } diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java 2016-01-29 08:23:46.000000000 +0000 @@ -834,9 +834,6 @@ Attribute filenameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.FILENAME); Attribute nameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.NAME); Attribute contentTypeAttribute = currentFieldAttributes.get(HttpHeaders.Names.CONTENT_TYPE); - if (contentTypeAttribute == null) { - throw new ErrorDataDecoderException("Content-Type is absent but required"); - } Attribute lengthAttribute = currentFieldAttributes.get(HttpHeaders.Names.CONTENT_LENGTH); long size; try { @@ -847,9 +844,15 @@ size = 0; } try { + String contentType; + if (contentTypeAttribute != null) { + contentType = contentTypeAttribute.getValue(); + } else { + contentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE; + } currentFileUpload = factory.createFileUpload(request, cleanString(nameAttribute.getValue()), cleanString(filenameAttribute.getValue()), - contentTypeAttribute.getValue(), mechanism.value(), localCharset, + contentType, mechanism.value(), localCharset, size); } catch (NullPointerException e) { throw new ErrorDataDecoderException(e); diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java 2016-01-29 08:23:46.000000000 +0000 @@ -54,8 +54,8 @@ */ public enum EncoderMode { /** - * Legacy mode which should work for most. It is known to not work with OAUTH. For OAUTH use - * {@link EncoderMode#RFC3986}. The W3C form recommentations this for submitting post form data. + * Legacy mode which should work for most. It is known to not work with OAUTH. For OAUTH use + * {@link EncoderMode#RFC3986}. The W3C form recommendations this for submitting post form data. */ RFC1738, diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InternalAttribute.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InternalAttribute.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InternalAttribute.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InternalAttribute.java 2016-01-29 08:23:46.000000000 +0000 @@ -79,10 +79,10 @@ @Override public boolean equals(Object o) { - if (!(o instanceof Attribute)) { + if (!(o instanceof InternalAttribute)) { return false; } - Attribute attribute = (Attribute) o; + InternalAttribute attribute = (InternalAttribute) o; return getName().equalsIgnoreCase(attribute.getName()); } diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java 2016-01-29 08:23:46.000000000 +0000 @@ -123,14 +123,7 @@ // Get path URI wsURL = uri(); - String path = wsURL.getPath(); - if (wsURL.getQuery() != null && !wsURL.getQuery().isEmpty()) { - path = wsURL.getPath() + '?' + wsURL.getQuery(); - } - - if (path == null || path.isEmpty()) { - path = "/"; - } + String path = rawPath(wsURL); // Format request FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path); diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java 2016-01-29 08:23:46.000000000 +0000 @@ -92,14 +92,7 @@ protected FullHttpRequest newHandshakeRequest() { // Get path URI wsURL = uri(); - String path = wsURL.getPath(); - if (wsURL.getQuery() != null && !wsURL.getQuery().isEmpty()) { - path = wsURL.getPath() + '?' + wsURL.getQuery(); - } - - if (path == null || path.isEmpty()) { - path = "/"; - } + String path = rawPath(wsURL); // Get 16 bit nonce and base 64 encode it byte[] nonce = WebSocketUtil.randomBytes(16); diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java 2016-01-29 08:23:46.000000000 +0000 @@ -92,14 +92,7 @@ protected FullHttpRequest newHandshakeRequest() { // Get path URI wsURL = uri(); - String path = wsURL.getPath(); - if (wsURL.getQuery() != null && !wsURL.getQuery().isEmpty()) { - path = wsURL.getPath() + '?' + wsURL.getQuery(); - } - - if (path == null || path.isEmpty()) { - path = "/"; - } + String path = rawPath(wsURL); // Get 16 bit nonce and base 64 encode it byte[] nonce = WebSocketUtil.randomBytes(16); diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java 2016-01-29 08:23:46.000000000 +0000 @@ -92,14 +92,7 @@ protected FullHttpRequest newHandshakeRequest() { // Get path URI wsURL = uri(); - String path = wsURL.getPath(); - if (wsURL.getQuery() != null && !wsURL.getQuery().isEmpty()) { - path = wsURL.getPath() + '?' + wsURL.getQuery(); - } - - if (path == null || path.isEmpty()) { - path = "/"; - } + String path = rawPath(wsURL); // Get 16 bit nonce and base 64 encode it byte[] nonce = WebSocketUtil.randomBytes(16); diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java 2016-01-29 08:23:46.000000000 +0000 @@ -405,4 +405,17 @@ } return channel.writeAndFlush(frame, promise); } + + /** + * Return the constructed raw path for the give {@link URI}. + */ + static String rawPath(URI wsURL) { + String path = wsURL.getRawPath(); + String query = wsURL.getQuery(); + if (query != null && !query.isEmpty()) { + path = path + '?' + query; + } + + return path == null || path.isEmpty() ? "/" : path; + } } diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketUtil.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketUtil.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketUtil.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketUtil.java 2016-01-29 08:23:46.000000000 +0000 @@ -19,6 +19,8 @@ import io.netty.buffer.Unpooled; import io.netty.handler.codec.base64.Base64; import io.netty.util.CharsetUtil; +import io.netty.util.concurrent.FastThreadLocal; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -27,6 +29,32 @@ */ final class WebSocketUtil { + private static final FastThreadLocal MD5 = new FastThreadLocal() { + @Override + protected MessageDigest initialValue() throws Exception { + try { + //Try to get a MessageDigest that uses MD5 + return MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + //This shouldn't happen! How old is the computer? + throw new InternalError("MD5 not supported on this platform - Outdated?"); + } + } + }; + + private static final FastThreadLocal SHA1 = new FastThreadLocal() { + @Override + protected MessageDigest initialValue() throws Exception { + try { + //Try to get a MessageDigest that uses SHA1 + return MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + //This shouldn't happen! How old is the computer? + throw new InternalError("SHA-1 not supported on this platform - Outdated?"); + } + } + }; + /** * Performs a MD5 hash on the specified data * @@ -34,15 +62,8 @@ * @return The hashed data */ static byte[] md5(byte[] data) { - try { - //Try to get a MessageDigest that uses MD5 - MessageDigest md = MessageDigest.getInstance("MD5"); - //Hash the data - return md.digest(data); - } catch (NoSuchAlgorithmException e) { - //This shouldn't happen! How old is the computer? - throw new InternalError("MD5 not supported on this platform - Outdated?"); - } + // TODO(normanmaurer): Create md5 method that not need MessageDigest. + return digest(MD5, data); } /** @@ -52,15 +73,14 @@ * @return The hashed data */ static byte[] sha1(byte[] data) { - try { - //Attempt to get a MessageDigest that uses SHA1 - MessageDigest md = MessageDigest.getInstance("SHA1"); - //Hash the data - return md.digest(data); - } catch (NoSuchAlgorithmException e) { - //Alright, you might have an old system. - throw new InternalError("SHA-1 is not supported on this platform - Outdated?"); - } + // TODO(normanmaurer): Create sha1 method that not need MessageDigest. + return digest(SHA1, data); + } + + private static byte[] digest(FastThreadLocal digestFastThreadLocal, byte[] data) { + MessageDigest digest = digestFastThreadLocal.get(); + digest.reset(); + return digest.digest(data); } /** diff -Nru netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java --- netty-4.0.33/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java 2016-01-29 08:23:46.000000000 +0000 @@ -323,9 +323,7 @@ } } - private final class StreamComparator implements Comparator, Serializable { - - private static final long serialVersionUID = 1161471649740544848L; + private final class StreamComparator implements Comparator { StreamComparator() { } diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieEncoderTest.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieEncoderTest.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieEncoderTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieEncoderTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -23,21 +23,23 @@ @Test public void testEncodingMultipleClientCookies() { - String c1 = "myCookie=myValue; "; - String c2 = "myCookie2=myValue2; "; + String c1 = "myCookie=myValue"; + String c2 = "myCookie2=myValue2"; String c3 = "myCookie3=myValue3"; - Cookie cookie = new DefaultCookie("myCookie", "myValue"); - cookie.setDomain(".adomainsomewhere"); - cookie.setMaxAge(50); - cookie.setPath("/apathsomewhere"); - cookie.setSecure(true); + Cookie cookie1 = new DefaultCookie("myCookie", "myValue"); + cookie1.setDomain(".adomainsomewhere"); + cookie1.setMaxAge(50); + cookie1.setPath("/apathsomewhere"); + cookie1.setSecure(true); Cookie cookie2 = new DefaultCookie("myCookie2", "myValue2"); cookie2.setDomain(".anotherdomainsomewhere"); cookie2.setPath("/anotherpathsomewhere"); cookie2.setSecure(false); Cookie cookie3 = new DefaultCookie("myCookie3", "myValue3"); - String encodedCookie = ClientCookieEncoder.STRICT.encode(cookie, cookie2, cookie3); - assertEquals(c1 + c2 + c3, encodedCookie); + String encodedCookie = ClientCookieEncoder.STRICT.encode(cookie1, cookie2, cookie3); + // Cookies should be sorted into decreasing order of path length, as per RFC6265. + // When no path is provided, we assume maximum path length (so cookie3 comes first). + assertEquals(c3 + "; " + c2 + "; " + c1, encodedCookie); } @Test diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -15,17 +15,20 @@ */ package io.netty.handler.codec.http.cookie; -import org.junit.Test; - +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import io.netty.handler.codec.http.HttpHeaderDateFormat; import java.text.ParseException; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static org.junit.Assert.*; +import org.junit.Test; public class ServerCookieEncoderTest { @@ -60,4 +63,29 @@ assertNotNull(encodedCookie2); assertTrue(encodedCookie2.isEmpty()); } + + @Test + public void testEncodingMultipleCookiesStrict() { + List result = new ArrayList(); + result.add("cookie2=value2"); + result.add("cookie1=value3"); + Cookie cookie1 = new DefaultCookie("cookie1", "value1"); + Cookie cookie2 = new DefaultCookie("cookie2", "value2"); + Cookie cookie3 = new DefaultCookie("cookie1", "value3"); + List encodedCookies = ServerCookieEncoder.STRICT.encode(cookie1, cookie2, cookie3); + assertEquals(result, encodedCookies); + } + + @Test + public void testEncodingMultipleCookiesLax() { + List result = new ArrayList(); + result.add("cookie1=value1"); + result.add("cookie2=value2"); + result.add("cookie1=value3"); + Cookie cookie1 = new DefaultCookie("cookie1", "value1"); + Cookie cookie2 = new DefaultCookie("cookie2", "value2"); + Cookie cookie3 = new DefaultCookie("cookie1", "value3"); + List encodedCookies = ServerCookieEncoder.LAX.encode(cookie1, cookie2, cookie3); + assertEquals(result, encodedCookies); + } } diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -74,9 +74,12 @@ headers.add(headers); } - @Test(expected = IllegalArgumentException.class) - public void testSetSelf() { + @Test + public void testSetSelfIsNoOp() { HttpHeaders headers = new DefaultHttpHeaders(false); + headers.add("some", "thing"); headers.set(headers); + Assert.assertEquals(1, headers.entries().size()); + Assert.assertEquals("thing", headers.get("some")); } } diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -376,4 +376,34 @@ assertEquals("tmp 0.txt", fileUpload.getFilename()); decoder.destroy(); } + + @Test + public void testMultipartRequestWithoutContentTypeBody() { + final String boundary = "dLV9Wyq26L_-JQxk6ferf-RT153LhOO"; + + final DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, + "http://localhost"); + + req.setDecoderResult(DecoderResult.SUCCESS); + req.headers().add(HttpHeaders.Names.CONTENT_TYPE, "multipart/form-data; boundary=" + boundary); + req.headers().add(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); + + // Force to use memory-based data. + final DefaultHttpDataFactory inMemoryFactory = new DefaultHttpDataFactory(false); + + for (String data : Arrays.asList("", "\r", "\r\r", "\r\r\r")) { + final String body = + "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"tmp-0.txt\"\r\n" + + "\r\n" + + data + "\r\n" + + "--" + boundary + "--\r\n"; + + req.content().writeBytes(body.getBytes(CharsetUtil.UTF_8)); + } + // Create decoder instance to test without any exception. + final HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(inMemoryFactory, req); + assertFalse(decoder.getBodyHttpDatas().isEmpty()); + decoder.destroy(); + } } diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,25 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http.websocketx; + +import java.net.URI; + +public class WebSocketClientHandshaker00Test extends WebSocketClientHandshakerTest { + @Override + protected WebSocketClientHandshaker newHandshaker(URI uri) { + return new WebSocketClientHandshaker00(uri, WebSocketVersion.V00, null, null, 1024); + } +} diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,25 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http.websocketx; + +import java.net.URI; + +public class WebSocketClientHandshaker07Test extends WebSocketClientHandshakerTest { + @Override + protected WebSocketClientHandshaker newHandshaker(URI uri) { + return new WebSocketClientHandshaker07(uri, WebSocketVersion.V07, null, false, null, 1024); + } +} diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,25 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http.websocketx; + +import java.net.URI; + +public class WebSocketClientHandshaker08Test extends WebSocketClientHandshakerTest { + @Override + protected WebSocketClientHandshaker newHandshaker(URI uri) { + return new WebSocketClientHandshaker07(uri, WebSocketVersion.V08, null, false, null, 1024); + } +} diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,25 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http.websocketx; + +import java.net.URI; + +public class WebSocketClientHandshaker13Test extends WebSocketClientHandshakerTest { + @Override + protected WebSocketClientHandshaker newHandshaker(URI uri) { + return new WebSocketClientHandshaker13(uri, WebSocketVersion.V13, null, false, null, 1024); + } +} diff -Nru netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java --- netty-4.0.33/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,39 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http.websocketx; + +import io.netty.handler.codec.http.FullHttpRequest; +import org.junit.Test; + +import java.net.URI; + +import static org.junit.Assert.assertEquals; + +public abstract class WebSocketClientHandshakerTest { + protected abstract WebSocketClientHandshaker newHandshaker(URI uri); + + @Test + public void testRawPath() { + URI uri = URI.create("ws://localhost:9999/path%20with%20ws"); + WebSocketClientHandshaker handshaker = newHandshaker(uri); + FullHttpRequest request = handshaker.newHandshakeRequest(); + try { + assertEquals("/path%20with%20ws", request.getUri()); + } finally { + request.release(); + } + } +} diff -Nru netty-4.0.33/codec-socks/pom.xml netty-4.0.34/codec-socks/pom.xml --- netty-4.0.33/codec-socks/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/codec-socks/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-codec-socks diff -Nru netty-4.0.33/common/pom.xml netty-4.0.34/common/pom.xml --- netty-4.0.33/common/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -21,7 +21,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-common diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/concurrent/AbstractFuture.java netty-4.0.34/common/src/main/java/io/netty/util/concurrent/AbstractFuture.java --- netty-4.0.33/common/src/main/java/io/netty/util/concurrent/AbstractFuture.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/concurrent/AbstractFuture.java 2016-01-29 08:23:46.000000000 +0000 @@ -15,6 +15,7 @@ */ package io.netty.util.concurrent; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -34,6 +35,9 @@ if (cause == null) { return getNow(); } + if (cause instanceof CancellationException) { + throw (CancellationException) cause; + } throw new ExecutionException(cause); } @@ -44,6 +48,9 @@ if (cause == null) { return getNow(); } + if (cause instanceof CancellationException) { + throw (CancellationException) cause; + } throw new ExecutionException(cause); } throw new TimeoutException(); diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/concurrent/DefaultPromise.java netty-4.0.34/common/src/main/java/io/netty/util/concurrent/DefaultPromise.java --- netty-4.0.33/common/src/main/java/io/netty/util/concurrent/DefaultPromise.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/concurrent/DefaultPromise.java 2016-01-29 08:23:46.000000000 +0000 @@ -18,6 +18,7 @@ import io.netty.util.Signal; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.InternalThreadLocalMap; +import io.netty.util.internal.OneTimeTask; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.logging.InternalLogger; @@ -27,7 +28,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; -import static java.util.concurrent.TimeUnit.*; +import static java.util.concurrent.TimeUnit.MILLISECONDS; public class DefaultPromise extends AbstractFuture implements Promise { @@ -576,7 +577,7 @@ if (listeners instanceof DefaultFutureListeners) { final DefaultFutureListeners dfl = (DefaultFutureListeners) listeners; - execute(executor, new Runnable() { + execute(executor, new OneTimeTask() { @Override public void run() { notifyListeners0(DefaultPromise.this, dfl); @@ -586,7 +587,7 @@ } else { final GenericFutureListener> l = (GenericFutureListener>) listeners; - execute(executor, new Runnable() { + execute(executor, new OneTimeTask() { @Override public void run() { notifyListener0(DefaultPromise.this, l); @@ -612,7 +613,9 @@ private void notifyLateListener(final GenericFutureListener l) { final EventExecutor executor = executor(); if (executor.inEventLoop()) { - if (listeners == null && lateListeners == null) { + // Execute immediately if late listeners is empty. This allows subsequent late listeners + // that are added after completion to be notified immediately and preserver order. + if (listeners == null && (lateListeners == null || lateListeners.isEmpty())) { final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get(); final int stackDepth = threadLocals.futureListenerStackDepth(); if (stackDepth < MAX_LISTENER_STACK_DEPTH) { @@ -658,7 +661,7 @@ } } - execute(eventExecutor, new Runnable() { + execute(eventExecutor, new OneTimeTask() { @Override public void run() { notifyListener0(future, l); @@ -752,7 +755,7 @@ if (listeners instanceof GenericProgressiveFutureListener[]) { final GenericProgressiveFutureListener[] array = (GenericProgressiveFutureListener[]) listeners; - execute(executor, new Runnable() { + execute(executor, new OneTimeTask() { @Override public void run() { notifyProgressiveListeners0(self, array, progress, total); @@ -761,7 +764,7 @@ } else { final GenericProgressiveFutureListener> l = (GenericProgressiveFutureListener>) listeners; - execute(executor, new Runnable() { + execute(executor, new OneTimeTask() { @Override public void run() { notifyProgressiveListener0(self, l, progress, total); diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/DomainMappingBuilder.java netty-4.0.34/common/src/main/java/io/netty/util/DomainMappingBuilder.java --- netty-4.0.33/common/src/main/java/io/netty/util/DomainMappingBuilder.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/DomainMappingBuilder.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,191 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.util; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; + +/** + * Builder for immutable {@link DomainNameMapping} instances. + * + * @param concrete type of value objects + */ +public final class DomainMappingBuilder { + + private final V defaultValue; + private final Map map; + + /** + * Constructor with default initial capacity of the map holding the mappings + * + * @param defaultValue the default value for {@link DomainNameMapping#map(String)} to return + * when nothing matches the input + */ + public DomainMappingBuilder(V defaultValue) { + this(4, defaultValue); + } + + /** + * Constructor with initial capacity of the map holding the mappings + * + * @param initialCapacity initial capacity for the internal map + * @param defaultValue the default value for {@link DomainNameMapping#map(String)} to return + * when nothing matches the input + */ + public DomainMappingBuilder(int initialCapacity, V defaultValue) { + this.defaultValue = checkNotNull(defaultValue, "defaultValue"); + this.map = new LinkedHashMap(initialCapacity); + } + + /** + * Adds a mapping that maps the specified (optionally wildcard) host name to the specified output value. + * Null values are forbidden for both hostnames and values. + *

+ * DNS wildcard is supported as hostname. + * For example, you can use {@code *.netty.io} to match {@code netty.io} and {@code downloads.netty.io}. + *

+ * + * @param hostname the host name (optionally wildcard) + * @param output the output value that will be returned by {@link DomainNameMapping#map(String)} + * when the specified host name matches the specified input host name + */ + public DomainMappingBuilder add(String hostname, V output) { + map.put(checkNotNull(hostname, "hostname"), checkNotNull(output, "output")); + return this; + } + + /** + * Creates a new instance of immutable {@link DomainNameMapping} + * Attempts to add new mappings to the result object will cause {@link UnsupportedOperationException} to be thrown + * + * @return new {@link DomainNameMapping} instance + */ + public DomainNameMapping build() { + return new ImmutableDomainNameMapping(this.defaultValue, this.map); + } + + /** + * Immutable mapping from domain name pattern to its associated value object. + * Mapping is represented by two arrays: keys and values. Key domainNamePatterns[i] is associated with values[i]. + * + * @param concrete type of value objects + */ + private static final class ImmutableDomainNameMapping extends DomainNameMapping { + private static final String REPR_HEADER = "ImmutableDomainNameMapping(default: "; + private static final String REPR_MAP_OPENING = ", map: {"; + private static final String REPR_MAP_CLOSING = "})"; + private static final int REPR_CONST_PART_LENGTH = + REPR_HEADER.length() + REPR_MAP_OPENING.length() + REPR_MAP_CLOSING.length(); + + private final String[] domainNamePatterns; + private final V[] values; + + @SuppressWarnings("unchecked") + private ImmutableDomainNameMapping(V defaultValue, Map map) { + super(null, defaultValue); + + Set> mappings = map.entrySet(); + int numberOfMappings = mappings.size(); + domainNamePatterns = new String[numberOfMappings]; + values = (V[]) new Object[numberOfMappings]; + + int index = 0; + for (Map.Entry mapping : mappings) { + domainNamePatterns[index] = normalizeHostname(mapping.getKey()); + values[index] = mapping.getValue(); + ++index; + } + } + + @Override + @Deprecated + public DomainNameMapping add(String hostname, V output) { + throw new UnsupportedOperationException( + "Immutable DomainNameMapping does not support modification after initial creation"); + } + + @Override + public V map(String hostname) { + if (hostname != null) { + hostname = normalizeHostname(hostname); + + int length = domainNamePatterns.length; + for (int index = 0; index < length; ++index) { + if (matches(domainNamePatterns[index], hostname)) { + return values[index]; + } + } + } + + return defaultValue; + } + + @Override + public String toString() { + String defaultValueStr = defaultValue.toString(); + + int numberOfMappings = domainNamePatterns.length; + if (numberOfMappings == 0) { + return REPR_HEADER + defaultValueStr + REPR_MAP_OPENING + REPR_MAP_CLOSING; + } + + String pattern0 = domainNamePatterns[0]; + String value0 = values[0].toString(); + int oneMappingLength = pattern0.length() + value0.length() + 3; // 2 for separator ", " and 1 for '=' + int estimatedBufferSize = estimateBufferSize(defaultValueStr.length(), numberOfMappings, oneMappingLength); + + StringBuilder sb = new StringBuilder(estimatedBufferSize) + .append(REPR_HEADER).append(defaultValueStr).append(REPR_MAP_OPENING); + + appendMapping(sb, pattern0, value0); + for (int index = 1; index < numberOfMappings; ++index) { + sb.append(", "); + appendMapping(sb, index); + } + + return sb.append(REPR_MAP_CLOSING).toString(); + } + + /** + * Estimates the length of string representation of the given instance: + * est = lengthOfConstantComponents + defaultValueLength + (estimatedMappingLength * numOfMappings) * 1.10 + * + * @param defaultValueLength length of string representation of {@link #defaultValue} + * @param numberOfMappings number of mappings the given instance holds, + * e.g. {@link #domainNamePatterns#length} + * @param estimatedMappingLength estimated size taken by one mapping + * @return estimated length of string returned by {@link #toString()} + */ + private static int estimateBufferSize(int defaultValueLength, + int numberOfMappings, + int estimatedMappingLength) { + return REPR_CONST_PART_LENGTH + defaultValueLength + + (int) (estimatedMappingLength * numberOfMappings * 1.10); + } + + private StringBuilder appendMapping(StringBuilder sb, int mappingIndex) { + return appendMapping(sb, domainNamePatterns[mappingIndex], values[mappingIndex].toString()); + } + + private static StringBuilder appendMapping(StringBuilder sb, String domainNamePattern, String value) { + return sb.append(domainNamePattern).append('=').append(value); + } + } +} diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/DomainNameMapping.java netty-4.0.34/common/src/main/java/io/netty/util/DomainNameMapping.java --- netty-4.0.33/common/src/main/java/io/netty/util/DomainNameMapping.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/DomainNameMapping.java 2016-01-29 08:23:46.000000000 +0000 @@ -22,7 +22,9 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; -import java.util.regex.Pattern; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; +import static io.netty.util.internal.StringUtil.commonSuffixOfLength; /** * Maps a domain name to its associated value object. @@ -33,18 +35,17 @@ */ public class DomainNameMapping implements Mapping { - private static final Pattern DNS_WILDCARD_PATTERN = Pattern.compile("^\\*\\..*"); - + final V defaultValue; private final Map map; - private final V defaultValue; - /** * Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping * will choose the one you add first. * * @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input + * @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead */ + @Deprecated public DomainNameMapping(V defaultValue) { this(4, defaultValue); } @@ -54,14 +55,17 @@ * will choose the one you add first. * * @param initialCapacity initial capacity for the internal map - * @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input + * @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input + * @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead */ + @Deprecated public DomainNameMapping(int initialCapacity, V defaultValue) { - if (defaultValue == null) { - throw new NullPointerException("defaultValue"); - } - map = new LinkedHashMap(initialCapacity); - this.defaultValue = defaultValue; + this(new LinkedHashMap(initialCapacity), defaultValue); + } + + DomainNameMapping(Map map, V defaultValue) { + this.defaultValue = checkNotNull(defaultValue, "defaultValue"); + this.map = map; } /** @@ -72,39 +76,31 @@ *

* * @param hostname the host name (optionally wildcard) - * @param output the output value that will be returned by {@link #map(String)} when the specified host name - * matches the specified input host name + * @param output the output value that will be returned by {@link #map(String)} when the specified host name + * matches the specified input host name + * @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead */ + @Deprecated public DomainNameMapping add(String hostname, V output) { - if (hostname == null) { - throw new NullPointerException("input"); - } - - if (output == null) { - throw new NullPointerException("output"); - } - - map.put(normalizeHostname(hostname), output); + map.put(normalizeHostname(checkNotNull(hostname, "hostname")), checkNotNull(output, "output")); return this; } /** * Simple function to match DNS wildcard. */ - private static boolean matches(String hostNameTemplate, String hostName) { - // note that inputs are converted and lowercased already - if (DNS_WILDCARD_PATTERN.matcher(hostNameTemplate).matches()) { - return hostNameTemplate.substring(2).equals(hostName) || - hostName.endsWith(hostNameTemplate.substring(1)); - } else { - return hostNameTemplate.equals(hostName); + static boolean matches(String template, String hostName) { + if (template.startsWith("*.")) { + return template.regionMatches(2, hostName, 0, hostName.length()) + || commonSuffixOfLength(hostName, template, template.length() - 1); } + return template.equals(hostName); } /** * IDNA ASCII conversion and case normalization */ - private static String normalizeHostname(String hostname) { + static String normalizeHostname(String hostname) { if (needsNormalization(hostname)) { hostname = IDN.toASCII(hostname, IDN.ALLOW_UNASSIGNED); } @@ -113,7 +109,7 @@ private static boolean needsNormalization(String hostname) { final int length = hostname.length(); - for (int i = 0; i < length; i ++) { + for (int i = 0; i < length; i++) { int c = hostname.charAt(i); if (c > 0x7F) { return true; @@ -123,17 +119,16 @@ } @Override - public V map(String input) { - if (input != null) { - input = normalizeHostname(input); + public V map(String hostname) { + if (hostname != null) { + hostname = normalizeHostname(hostname); for (Map.Entry entry : map.entrySet()) { - if (matches(entry.getKey(), input)) { + if (matches(entry.getKey(), hostname)) { return entry.getValue(); } } } - return defaultValue; } diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/internal/MathUtil.java netty-4.0.34/common/src/main/java/io/netty/util/internal/MathUtil.java --- netty-4.0.33/common/src/main/java/io/netty/util/internal/MathUtil.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/internal/MathUtil.java 2016-01-29 08:23:46.000000000 +0000 @@ -48,4 +48,19 @@ public static boolean isOutOfBounds(int index, int length, int capacity) { return (index | length | (index + length) | (capacity - (index + length))) < 0; } + + /** + * Compare to {@code long} values. + * @param x the first {@code long} to compare. + * @param y the second {@code long} to compare. + * @return + *
    + *
  • 0 if {@code x == y}
  • + *
  • {@code > 0} if {@code x > y}
  • + *
  • {@code < 0} if {@code x < y}
  • + *
+ */ + public static int compare(long x, long y) { + return (x < y) ? -1 : (x > y) ? 1 : 0; + } } diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/internal/PlatformDependent0.java netty-4.0.34/common/src/main/java/io/netty/util/internal/PlatformDependent0.java --- netty-4.0.33/common/src/main/java/io/netty/util/internal/PlatformDependent0.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/internal/PlatformDependent0.java 2016-01-29 08:23:46.000000000 +0000 @@ -269,17 +269,17 @@ } static AtomicReferenceFieldUpdater newAtomicReferenceFieldUpdater( - Class tclass, String fieldName) throws Exception { + Class tclass, String fieldName) throws Exception { return new UnsafeAtomicReferenceFieldUpdater(UNSAFE, tclass, fieldName); } static AtomicIntegerFieldUpdater newAtomicIntegerFieldUpdater( - Class tclass, String fieldName) throws Exception { + Class tclass, String fieldName) throws Exception { return new UnsafeAtomicIntegerFieldUpdater(UNSAFE, tclass, fieldName); } static AtomicLongFieldUpdater newAtomicLongFieldUpdater( - Class tclass, String fieldName) throws Exception { + Class tclass, String fieldName) throws Exception { return new UnsafeAtomicLongFieldUpdater(UNSAFE, tclass, fieldName); } diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/internal/PlatformDependent.java netty-4.0.34/common/src/main/java/io/netty/util/internal/PlatformDependent.java --- netty-4.0.33/common/src/main/java/io/netty/util/internal/PlatformDependent.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/internal/PlatformDependent.java 2016-01-29 08:23:46.000000000 +0000 @@ -88,7 +88,6 @@ private static final int BIT_MODE = bitMode0(); private static final int ADDRESS_SIZE = addressSize0(); - private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; static { if (logger.isDebugEnabled()) { @@ -416,7 +415,7 @@ * use {@link AtomicReferenceFieldUpdater#newUpdater(Class, Class, String)} as fallback. */ public static AtomicReferenceFieldUpdater newAtomicReferenceFieldUpdater( - Class tclass, String fieldName) { + Class tclass, String fieldName) { if (hasUnsafe()) { try { return PlatformDependent0.newAtomicReferenceFieldUpdater(tclass, fieldName); @@ -433,7 +432,7 @@ * use {@link AtomicIntegerFieldUpdater#newUpdater(Class, String)} as fallback. */ public static AtomicIntegerFieldUpdater newAtomicIntegerFieldUpdater( - Class tclass, String fieldName) { + Class tclass, String fieldName) { if (hasUnsafe()) { try { return PlatformDependent0.newAtomicIntegerFieldUpdater(tclass, fieldName); @@ -450,7 +449,7 @@ * use {@link AtomicLongFieldUpdater#newUpdater(Class, String)} as fallback. */ public static AtomicLongFieldUpdater newAtomicLongFieldUpdater( - Class tclass, String fieldName) { + Class tclass, String fieldName) { if (hasUnsafe()) { try { return PlatformDependent0.newAtomicLongFieldUpdater(tclass, fieldName); @@ -518,7 +517,7 @@ try { Class.forName("android.app.Application", false, getSystemClassLoader()); android = true; - } catch (Exception e) { + } catch (Throwable ignored) { // Failed to load the class uniquely available in Android. android = false; } @@ -565,7 +564,7 @@ // Ignore } } - } catch (Exception e) { + } catch (Throwable ignored) { // Failed to run the command. uid = null; } finally { @@ -646,7 +645,7 @@ Class.forName("java.time.Clock", false, getClassLoader(Object.class)); javaVersion = 8; break; - } catch (Exception e) { + } catch (Throwable ignored) { // Ignore } @@ -654,7 +653,7 @@ Class.forName("java.util.concurrent.LinkedTransferQueue", false, getClassLoader(BlockingQueue.class)); javaVersion = 7; break; - } catch (Exception e) { + } catch (Throwable ignored) { // Ignore } @@ -699,7 +698,7 @@ boolean hasUnsafe = PlatformDependent0.hasUnsafe(); logger.debug("sun.misc.Unsafe: {}", hasUnsafe ? "available" : "unavailable"); return hasUnsafe; - } catch (Throwable t) { + } catch (Throwable ignored) { // Probably failed to initialize PlatformDependent0. return false; } @@ -720,7 +719,7 @@ Class vmClass = Class.forName("sun.misc.VM", true, getSystemClassLoader()); Method m = vmClass.getDeclaredMethod("maxDirectMemory"); maxDirectMemory = ((Number) m.invoke(null)).longValue(); - } catch (Throwable t) { + } catch (Throwable ignored) { // Ignore } @@ -760,7 +759,7 @@ } break; } - } catch (Throwable t) { + } catch (Throwable ignored) { // Ignore } @@ -845,7 +844,7 @@ return f; } } - } catch (Exception ignored) { + } catch (Throwable ignored) { // Environment variable inaccessible } diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/internal/StringUtil.java netty-4.0.34/common/src/main/java/io/netty/util/internal/StringUtil.java --- netty-4.0.33/common/src/main/java/io/netty/util/internal/StringUtil.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/internal/StringUtil.java 2016-01-29 08:23:46.000000000 +0000 @@ -184,6 +184,18 @@ } /** + * Checks if two strings have the same suffix of specified length + * + * @param s string + * @param p string + * @param len length of the common suffix + * @return true if both s and p are not null and both have the same suffix. Otherwise - false + */ + public static boolean commonSuffixOfLength(String s, String p, int len) { + return s != null && p != null && len >= 0 && s.regionMatches(s.length() - len, p, p.length() - len, len); + } + + /** * Converts the specified byte value into a 2-digit hexadecimal integer. */ public static String byteToHexStringPadded(int value) { @@ -387,6 +399,17 @@ return s == null || s.isEmpty(); } + /** + * Determine if {@code c} lies within the range of values defined for + * Surrogate Code Point. + * @param c the character to check. + * @return {@code true} if {@code c} lies within the range of values defined for + * Surrogate Code Point. {@code false} otherwise. + */ + public static boolean isSurrogate(char c) { + return c >= '\uD800' && c <= '\uDFFF'; + } + private static boolean isDoubleQuote(char c) { return c == DOUBLE_QUOTE; } diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/internal/UnsafeAtomicIntegerFieldUpdater.java netty-4.0.34/common/src/main/java/io/netty/util/internal/UnsafeAtomicIntegerFieldUpdater.java --- netty-4.0.33/common/src/main/java/io/netty/util/internal/UnsafeAtomicIntegerFieldUpdater.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/internal/UnsafeAtomicIntegerFieldUpdater.java 2016-01-29 08:23:46.000000000 +0000 @@ -25,7 +25,8 @@ private final long offset; private final Unsafe unsafe; - UnsafeAtomicIntegerFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) throws NoSuchFieldException { + UnsafeAtomicIntegerFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) + throws NoSuchFieldException { Field field = tClass.getDeclaredField(fieldName); if (!Modifier.isVolatile(field.getModifiers())) { throw new IllegalArgumentException("Must be volatile"); diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/internal/UnsafeAtomicLongFieldUpdater.java netty-4.0.34/common/src/main/java/io/netty/util/internal/UnsafeAtomicLongFieldUpdater.java --- netty-4.0.33/common/src/main/java/io/netty/util/internal/UnsafeAtomicLongFieldUpdater.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/internal/UnsafeAtomicLongFieldUpdater.java 2016-01-29 08:23:46.000000000 +0000 @@ -25,7 +25,8 @@ private final long offset; private final Unsafe unsafe; - UnsafeAtomicLongFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) throws NoSuchFieldException { + UnsafeAtomicLongFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) + throws NoSuchFieldException { Field field = tClass.getDeclaredField(fieldName); if (!Modifier.isVolatile(field.getModifiers())) { throw new IllegalArgumentException("Must be volatile"); diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/internal/UnsafeAtomicReferenceFieldUpdater.java netty-4.0.34/common/src/main/java/io/netty/util/internal/UnsafeAtomicReferenceFieldUpdater.java --- netty-4.0.33/common/src/main/java/io/netty/util/internal/UnsafeAtomicReferenceFieldUpdater.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/internal/UnsafeAtomicReferenceFieldUpdater.java 2016-01-29 08:23:46.000000000 +0000 @@ -25,7 +25,8 @@ private final long offset; private final Unsafe unsafe; - UnsafeAtomicReferenceFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) throws NoSuchFieldException { + UnsafeAtomicReferenceFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) + throws NoSuchFieldException { Field field = tClass.getDeclaredField(fieldName); if (!Modifier.isVolatile(field.getModifiers())) { throw new IllegalArgumentException("Must be volatile"); diff -Nru netty-4.0.33/common/src/main/java/io/netty/util/ThreadDeathWatcher.java netty-4.0.34/common/src/main/java/io/netty/util/ThreadDeathWatcher.java --- netty-4.0.33/common/src/main/java/io/netty/util/ThreadDeathWatcher.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/main/java/io/netty/util/ThreadDeathWatcher.java 2016-01-29 08:23:46.000000000 +0000 @@ -19,6 +19,8 @@ import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.internal.MpscLinkedQueueNode; import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.StringUtil; +import io.netty.util.internal.SystemPropertyUtil; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -40,14 +42,22 @@ public final class ThreadDeathWatcher { private static final InternalLogger logger = InternalLoggerFactory.getInstance(ThreadDeathWatcher.class); - private static final ThreadFactory threadFactory = - new DefaultThreadFactory(ThreadDeathWatcher.class, true, Thread.MIN_PRIORITY); + private static final ThreadFactory threadFactory; private static final Queue pendingEntries = PlatformDependent.newMpscQueue(); private static final Watcher watcher = new Watcher(); private static final AtomicBoolean started = new AtomicBoolean(); private static volatile Thread watcherThread; + static { + String poolName = "threadDeathWatcher"; + String serviceThreadPrefix = SystemPropertyUtil.get("io.netty.serviceThreadPrefix"); + if (!StringUtil.isNullOrEmpty(serviceThreadPrefix)) { + poolName = serviceThreadPrefix + poolName; + } + threadFactory = new DefaultThreadFactory(poolName, true, Thread.MIN_PRIORITY); + } + /** * Schedules the specified {@code task} to run when the specified {@code thread} dies. * diff -Nru netty-4.0.33/common/src/test/java/io/netty/util/concurrent/DefaultPromiseTest.java netty-4.0.34/common/src/test/java/io/netty/util/concurrent/DefaultPromiseTest.java --- netty-4.0.33/common/src/test/java/io/netty/util/concurrent/DefaultPromiseTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/test/java/io/netty/util/concurrent/DefaultPromiseTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -19,17 +19,39 @@ import org.junit.Test; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; @SuppressWarnings("unchecked") public class DefaultPromiseTest { + @Test(expected = CancellationException.class) + public void testCancellationExceptionIsThrownWhenBlockingGet() throws InterruptedException, ExecutionException { + final Promise promise = new DefaultPromise(ImmediateEventExecutor.INSTANCE); + promise.cancel(false); + promise.get(); + } + + @Test(expected = CancellationException.class) + public void testCancellationExceptionIsThrownWhenBlockingGetWithTimeout() throws InterruptedException, + ExecutionException, TimeoutException { + final Promise promise = new DefaultPromise(ImmediateEventExecutor.INSTANCE); + promise.cancel(false); + promise.get(1, TimeUnit.SECONDS); + } + @Test public void testNoStackOverflowErrorWithImmediateEventExecutorA() throws Exception { final Promise[] p = new DefaultPromise[128]; @@ -84,54 +106,56 @@ @Test public void testListenerNotifyOrder() throws Exception { EventExecutor executor = new TestEventExecutor(); - final BlockingQueue> listeners = new LinkedBlockingQueue>(); - int runs = 100000; - - for (int i = 0; i < runs; i++) { - final Promise promise = new DefaultPromise(executor); - final FutureListener listener1 = new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - listeners.add(this); - } - }; - final FutureListener listener2 = new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - listeners.add(this); - } - }; - final FutureListener listener4 = new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - listeners.add(this); - } - }; - final FutureListener listener3 = new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - // Ensure listener4 is notified *after* this method returns to maintain the order. - future.addListener(listener4); - listeners.add(this); - } - }; + try { + final BlockingQueue> listeners = new LinkedBlockingQueue>(); + int runs = 100000; + + for (int i = 0; i < runs; i++) { + final Promise promise = new DefaultPromise(executor); + final FutureListener listener1 = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + listeners.add(this); + } + }; + final FutureListener listener2 = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + listeners.add(this); + } + }; + final FutureListener listener4 = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + listeners.add(this); + } + }; + final FutureListener listener3 = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + listeners.add(this); + future.addListener(listener4); + } + }; - GlobalEventExecutor.INSTANCE.execute(new Runnable() { - @Override - public void run() { - promise.setSuccess(null); - } - }); + GlobalEventExecutor.INSTANCE.execute(new Runnable() { + @Override + public void run() { + promise.setSuccess(null); + } + }); - promise.addListener(listener1).addListener(listener2).addListener(listener3); + promise.addListener(listener1).addListener(listener2).addListener(listener3); - assertSame("Fail during run " + i + " / " + runs, listener1, listeners.take()); - assertSame("Fail during run " + i + " / " + runs, listener2, listeners.take()); - assertSame("Fail during run " + i + " / " + runs, listener3, listeners.take()); - assertSame("Fail during run " + i + " / " + runs, listener4, listeners.take()); - assertTrue("Fail during run " + i + " / " + runs, listeners.isEmpty()); + assertSame("Fail 1 during run " + i + " / " + runs, listener1, listeners.take()); + assertSame("Fail 2 during run " + i + " / " + runs, listener2, listeners.take()); + assertSame("Fail 3 during run " + i + " / " + runs, listener3, listeners.take()); + assertSame("Fail 4 during run " + i + " / " + runs, listener4, listeners.take()); + assertTrue("Fail during run " + i + " / " + runs, listeners.isEmpty()); + } + } finally { + executor.shutdownGracefully(0, 0, TimeUnit.SECONDS).sync(); } - executor.shutdownGracefully().sync(); } @Test @@ -145,7 +169,7 @@ @Test(timeout = 2000) public void testPromiseListenerAddWhenCompleteFailure() throws Exception { - testPromiseListenerAddWhenComplete(new RuntimeException()); + testPromiseListenerAddWhenComplete(fakeException()); } @Test(timeout = 2000) @@ -153,6 +177,93 @@ testPromiseListenerAddWhenComplete(null); } + @Test(timeout = 2000) + public void testLateListenerIsOrderedCorrectlySuccess() throws InterruptedException { + testLateListenerIsOrderedCorrectly(null); + } + + @Test(timeout = 2000) + public void testLateListenerIsOrderedCorrectlyFailure() throws InterruptedException { + testLateListenerIsOrderedCorrectly(fakeException()); + } + + /** + * This test is mean to simulate the following sequence of events, which all take place on the I/O thread: + *
    + *
  1. A write is done
  2. + *
  3. The write operation completes, and the promise state is changed to done
  4. + *
  5. A listener is added to the return from the write. The {@link FutureListener#operationComplete()} updates + * state which must be invoked before the response to the previous write is read.
  6. + *
  7. The write operation
  8. + *
+ */ + private static void testLateListenerIsOrderedCorrectly(Throwable cause) throws InterruptedException { + final EventExecutor executor = new TestEventExecutor(); + try { + final AtomicInteger state = new AtomicInteger(); + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(2); + final Promise promise = new DefaultPromise(executor); + + // Add a listener before completion so "lateListener" is used next time we add a listener. + promise.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + assertTrue(state.compareAndSet(0, 1)); + } + }); + + // Simulate write operation completing, which will execute listeners in another thread. + if (cause == null) { + promise.setSuccess(null); + } else { + promise.setFailure(cause); + } + + // Add a "late listener" + promise.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + assertTrue(state.compareAndSet(1, 2)); + latch1.countDown(); + } + }); + + // Wait for the listeners and late listeners to be completed. + latch1.await(); + assertEquals(2, state.get()); + + // This is the important listener. A late listener that is added after all late listeners + // have completed, and needs to update state before a read operation (on the same executor). + executor.execute(new Runnable() { + @Override + public void run() { + promise.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + assertTrue(state.compareAndSet(2, 3)); + latch2.countDown(); + } + }); + } + }); + + // Simulate a read operation being queued up in the executor. + executor.execute(new Runnable() { + @Override + public void run() { + // This is the key, we depend upon the state being set in the next listener. + assertEquals(3, state.get()); + latch2.countDown(); + } + }); + + latch2.await(); + } finally { + executor.shutdownGracefully(0, 0, TimeUnit.SECONDS).sync(); + } + } + private static void testPromiseListenerAddWhenComplete(Throwable cause) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final Promise promise = new DefaultPromise(ImmediateEventExecutor.INSTANCE); @@ -228,4 +339,8 @@ } } } + + private static RuntimeException fakeException() { + return new RuntimeException("fake exception"); + } } diff -Nru netty-4.0.33/common/src/test/java/io/netty/util/DomainNameMappingTest.java netty-4.0.34/common/src/test/java/io/netty/util/DomainNameMappingTest.java --- netty-4.0.33/common/src/test/java/io/netty/util/DomainNameMappingTest.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/common/src/test/java/io/netty/util/DomainNameMappingTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,184 @@ +/* +* Copyright 2015 The Netty Project +* +* The Netty Project licenses this file to you under the Apache License, +* version 2.0 (the "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at: +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +*/ + +package io.netty.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +@SuppressWarnings("deprecation") +public class DomainNameMappingTest { + + // Deprecated API + + @Test(expected = NullPointerException.class) + public void testNullDefaultValueInDeprecatedApi() { + new DomainNameMapping(null); + } + + @Test(expected = NullPointerException.class) + public void testNullDomainNamePatternsAreForbiddenInDeprecatedApi() { + new DomainNameMapping("NotFound").add(null, "Some value"); + } + + @Test(expected = NullPointerException.class) + public void testNullValuesAreForbiddenInDeprecatedApi() { + new DomainNameMapping("NotFound").add("Some key", null); + } + + @Test + public void testDefaultValueInDeprecatedApi() { + DomainNameMapping mapping = new DomainNameMapping("NotFound"); + + assertEquals("NotFound", mapping.map("not-existing")); + + mapping.add("*.netty.io", "Netty"); + + assertEquals("NotFound", mapping.map("not-existing")); + } + + @Test + public void testStrictEqualityInDeprecatedApi() { + DomainNameMapping mapping = new DomainNameMapping("NotFound") + .add("netty.io", "Netty") + .add("downloads.netty.io", "Netty-Downloads"); + + assertEquals("Netty", mapping.map("netty.io")); + assertEquals("Netty-Downloads", mapping.map("downloads.netty.io")); + + assertEquals("NotFound", mapping.map("x.y.z.netty.io")); + } + + @Test + public void testWildcardMatchesAnyPrefixInDeprecatedApi() { + DomainNameMapping mapping = new DomainNameMapping("NotFound") + .add("*.netty.io", "Netty"); + + assertEquals("Netty", mapping.map("netty.io")); + assertEquals("Netty", mapping.map("downloads.netty.io")); + assertEquals("Netty", mapping.map("x.y.z.netty.io")); + + assertEquals("NotFound", mapping.map("netty.io.x")); + } + + @Test + public void testFirstMatchWinsInDeprecatedApi() { + assertEquals("Netty", + new DomainNameMapping("NotFound") + .add("*.netty.io", "Netty") + .add("downloads.netty.io", "Netty-Downloads") + .map("downloads.netty.io")); + + assertEquals("Netty-Downloads", + new DomainNameMapping("NotFound") + .add("downloads.netty.io", "Netty-Downloads") + .add("*.netty.io", "Netty") + .map("downloads.netty.io")); + } + + @Test + public void testToStringInDeprecatedApi() { + DomainNameMapping mapping = new DomainNameMapping("NotFound") + .add("*.netty.io", "Netty") + .add("downloads.netty.io", "Netty-Downloads"); + + assertEquals( + "DomainNameMapping(default: NotFound, map: {*.netty.io=Netty, downloads.netty.io=Netty-Downloads})", + mapping.toString()); + } + + // Immutable DomainNameMapping Builder API + + @Test(expected = NullPointerException.class) + public void testNullDefaultValue() { + new DomainMappingBuilder(null); + } + + @Test(expected = NullPointerException.class) + public void testNullDomainNamePatternsAreForbidden() { + new DomainMappingBuilder("NotFound").add(null, "Some value"); + } + + @Test(expected = NullPointerException.class) + public void testNullValuesAreForbidden() { + new DomainMappingBuilder("NotFound").add("Some key", null); + } + + @Test + public void testDefaultValue() { + DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + .add("*.netty.io", "Netty") + .build(); + + assertEquals("NotFound", mapping.map("not-existing")); + } + + @Test + public void testStrictEquality() { + DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + .add("netty.io", "Netty") + .add("downloads.netty.io", "Netty-Downloads") + .build(); + + assertEquals("Netty", mapping.map("netty.io")); + assertEquals("Netty-Downloads", mapping.map("downloads.netty.io")); + + assertEquals("NotFound", mapping.map("x.y.z.netty.io")); + } + + @Test + public void testWildcardMatchesAnyPrefix() { + DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + .add("*.netty.io", "Netty") + .build(); + + assertEquals("Netty", mapping.map("netty.io")); + assertEquals("Netty", mapping.map("downloads.netty.io")); + assertEquals("Netty", mapping.map("x.y.z.netty.io")); + + assertEquals("NotFound", mapping.map("netty.io.x")); + } + + @Test + public void testFirstMatchWins() { + assertEquals("Netty", + new DomainMappingBuilder("NotFound") + .add("*.netty.io", "Netty") + .add("downloads.netty.io", "Netty-Downloads") + .build() + .map("downloads.netty.io")); + + assertEquals("Netty-Downloads", + new DomainMappingBuilder("NotFound") + .add("downloads.netty.io", "Netty-Downloads") + .add("*.netty.io", "Netty") + .build() + .map("downloads.netty.io")); + } + + @Test + public void testToString() { + DomainNameMapping mapping = new DomainMappingBuilder("NotFound") + .add("*.netty.io", "Netty") + .add("downloads.netty.io", "Netty-Download") + .build(); + + assertEquals( + "ImmutableDomainNameMapping(default: NotFound, map: {*.netty.io=Netty, downloads.netty.io=Netty-Download})", + mapping.toString()); + } +} diff -Nru netty-4.0.33/common/src/test/java/io/netty/util/internal/StringUtilTest.java netty-4.0.34/common/src/test/java/io/netty/util/internal/StringUtilTest.java --- netty-4.0.33/common/src/test/java/io/netty/util/internal/StringUtilTest.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/common/src/test/java/io/netty/util/internal/StringUtilTest.java 2016-01-29 08:23:46.000000000 +0000 @@ -83,6 +83,245 @@ } @Test + public void commonSuffixOfLengthTest() { + // negative length suffixes are never common + checkNotCommonSuffix("abc", "abc", -1); + + // null has no suffix + checkNotCommonSuffix("abc", null, 0); + checkNotCommonSuffix(null, null, 0); + + // any non-null string has 0-length suffix + checkCommonSuffix("abc", "xx", 0); + + checkCommonSuffix("abc", "abc", 0); + checkCommonSuffix("abc", "abc", 1); + checkCommonSuffix("abc", "abc", 2); + checkCommonSuffix("abc", "abc", 3); + checkNotCommonSuffix("abc", "abc", 4); + + checkCommonSuffix("abcd", "cd", 1); + checkCommonSuffix("abcd", "cd", 2); + checkNotCommonSuffix("abcd", "cd", 3); + + checkCommonSuffix("abcd", "axcd", 1); + checkCommonSuffix("abcd", "axcd", 2); + checkNotCommonSuffix("abcd", "axcd", 3); + + checkNotCommonSuffix("abcx", "abcy", 1); + } + + private static void checkNotCommonSuffix(String s, String p, int len) { + assertFalse(checkCommonSuffixSymmetric(s, p, len)); + } + + private static void checkCommonSuffix(String s, String p, int len) { + assertTrue(checkCommonSuffixSymmetric(s, p, len)); + } + + private static boolean checkCommonSuffixSymmetric(String s, String p, int len) { + boolean sp = commonSuffixOfLength(s, p, len); + boolean ps = commonSuffixOfLength(p, s, len); + assertEquals(sp, ps); + return sp; + } + + @Test (expected = NullPointerException.class) + public void escapeCsvNull() { + StringUtil.escapeCsv(null); + } + + @Test + public void escapeCsvEmpty() { + CharSequence value = ""; + CharSequence expected = value; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvUnquoted() { + CharSequence value = "something"; + CharSequence expected = value; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvAlreadyQuoted() { + CharSequence value = "\"something\""; + CharSequence expected = "\"something\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithQuote() { + CharSequence value = "s\""; + CharSequence expected = "\"s\"\"\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithQuoteInMiddle() { + CharSequence value = "some text\"and more text"; + CharSequence expected = "\"some text\"\"and more text\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithQuoteInMiddleAlreadyQuoted() { + CharSequence value = "\"some text\"and more text\""; + CharSequence expected = "\"some text\"\"and more text\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithQuotedWords() { + CharSequence value = "\"foo\"\"goo\""; + CharSequence expected = "\"foo\"\"goo\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithAlreadyEscapedQuote() { + CharSequence value = "foo\"\"goo"; + CharSequence expected = "foo\"\"goo"; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvEndingWithQuote() { + CharSequence value = "some\""; + CharSequence expected = "\"some\"\"\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithSingleQuote() { + CharSequence value = "\""; + CharSequence expected = "\"\"\"\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithSingleQuoteAndCharacter() { + CharSequence value = "\"f"; + CharSequence expected = "\"\"\"f\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvAlreadyEscapedQuote() { + CharSequence value = "\"some\"\""; + CharSequence expected = "\"some\"\"\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvQuoted() { + CharSequence value = "\"foo,goo\""; + CharSequence expected = value; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithLineFeed() { + CharSequence value = "some text\n more text"; + CharSequence expected = "\"some text\n more text\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithSingleLineFeedCharacter() { + CharSequence value = "\n"; + CharSequence expected = "\"\n\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithMultipleLineFeedCharacter() { + CharSequence value = "\n\n"; + CharSequence expected = "\"\n\n\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithQuotedAndLineFeedCharacter() { + CharSequence value = " \" \n "; + CharSequence expected = "\" \"\" \n \""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithLineFeedAtEnd() { + CharSequence value = "testing\n"; + CharSequence expected = "\"testing\n\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithComma() { + CharSequence value = "test,ing"; + CharSequence expected = "\"test,ing\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithSingleComma() { + CharSequence value = ","; + CharSequence expected = "\",\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithSingleCarriageReturn() { + CharSequence value = "\r"; + CharSequence expected = "\"\r\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithMultipleCarriageReturn() { + CharSequence value = "\r\r"; + CharSequence expected = "\"\r\r\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithCarriageReturn() { + CharSequence value = "some text\r more text"; + CharSequence expected = "\"some text\r more text\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithQuotedAndCarriageReturnCharacter() { + CharSequence value = "\"\r"; + CharSequence expected = "\"\"\"\r\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithCarriageReturnAtEnd() { + CharSequence value = "testing\r"; + CharSequence expected = "\"testing\r\""; + escapeCsv(value, expected); + } + + @Test + public void escapeCsvWithCRLFCharacter() { + CharSequence value = "\r\n"; + CharSequence expected = "\"\r\n\""; + escapeCsv(value, expected); + } + + private static void escapeCsv(CharSequence value, CharSequence expected) { + CharSequence escapedValue = value; + for (int i = 0; i < 10; ++i) { + escapedValue = StringUtil.escapeCsv(escapedValue); + assertEquals(expected, escapedValue.toString()); + } + } + + @Test public void testSimpleClassName() throws Exception { testSimpleClassName(String.class); } diff -Nru netty-4.0.33/debian/changelog netty-4.0.34/debian/changelog --- netty-4.0.33/debian/changelog 2015-11-19 22:38:03.000000000 +0000 +++ netty-4.0.34/debian/changelog 2016-01-31 22:39:21.000000000 +0000 @@ -1,3 +1,13 @@ +netty (1:4.0.34-1) unstable; urgency=medium + + * Team upload. + * New upstream release + - Depend on netty-tcnative (>= 1.1.33.Fork11) + * Build with the DH sequencer instead of CDBS + * Made the versions.properties embedded in the jar files reproducible + + -- Emmanuel Bourg Sun, 31 Jan 2016 23:39:15 +0100 + netty (1:4.0.33-1) unstable; urgency=medium * Team upload. diff -Nru netty-4.0.33/debian/control netty-4.0.34/debian/control --- netty-4.0.33/debian/control 2015-11-19 22:27:45.000000000 +0000 +++ netty-4.0.34/debian/control 2016-01-31 22:38:33.000000000 +0000 @@ -8,7 +8,6 @@ Damien Raude-Morvan Build-Depends: ant, ant-contrib (>= 1.0~b3+svn177-7~), - cdbs, debhelper (>= 9), default-jdk, ivy, @@ -23,7 +22,7 @@ libmaven-dependency-plugin-java, libmaven-scm-java, libmockito-java, - libnetty-tcnative-java, + libnetty-tcnative-java (>= 1.1.33.Fork11), libprotobuf-java, libxz-java, maven-debian-helper (>= 1.5) diff -Nru netty-4.0.33/debian/patches/05-reproducible-versions-properties.patch netty-4.0.34/debian/patches/05-reproducible-versions-properties.patch --- netty-4.0.33/debian/patches/05-reproducible-versions-properties.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/debian/patches/05-reproducible-versions-properties.patch 2016-01-31 22:31:09.000000000 +0000 @@ -0,0 +1,22 @@ +Description: Make the versions.properties files reproducible +Author: Emmanuel Bourg +Forwarded: not-needed +--- a/pom.xml ++++ b/pom.xml +@@ -874,11 +874,11 @@ + + + +- +- +- +- +- ++ ++ ++ ++ ++ + + + diff -Nru netty-4.0.33/debian/patches/series netty-4.0.34/debian/patches/series --- netty-4.0.33/debian/patches/series 2015-11-19 22:27:45.000000000 +0000 +++ netty-4.0.34/debian/patches/series 2016-01-31 22:38:33.000000000 +0000 @@ -2,3 +2,4 @@ 02-ignore-enforcer-rules.patch 03-ignore-jboss-marshalling.patch 04-netty-all-light.patch +05-reproducible-versions-properties.patch diff -Nru netty-4.0.33/debian/rules netty-4.0.34/debian/rules --- netty-4.0.33/debian/rules 2015-11-19 22:27:45.000000000 +0000 +++ netty-4.0.34/debian/rules 2016-01-31 22:09:00.000000000 +0000 @@ -1,14 +1,12 @@ #!/usr/bin/make -f -include /usr/share/cdbs/1/rules/debhelper.mk -include /usr/share/cdbs/1/class/maven.mk +export BUILD_DATE=$(shell date --date='@${SOURCE_DATE_EPOCH}' --utc +'%Y-%m-%d %H:%M:%S') -JAVA_HOME := /usr/lib/jvm/default-java +%: + dh $@ --buildsystem=maven -common-configure-indep:: - # Create an empty manifest to workaround an issue with maven-jar-plugin - mkdir -p all/target/classes/META-INF/ - touch all/target/classes/META-INF/MANIFEST.MF +override_dh_auto_build: + dh_auto_build -- package -Dbuild.date='${BUILD_DATE}' get-orig-source: uscan --download-current-version --force-download --no-symlink diff -Nru netty-4.0.33/example/pom.xml netty-4.0.34/example/pom.xml --- netty-4.0.33/example/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/example/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -21,7 +21,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-example @@ -111,7 +111,7 @@ ${java.home}/bin/java ${argLine.common} - ${argLine.bootcp} + ${argLine.alpnAgent} -classpath %classpath ${argLine.leak} ${argLine.coverage} diff -Nru netty-4.0.33/handler/pom.xml netty-4.0.34/handler/pom.xml --- netty-4.0.33/handler/pom.xml 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/pom.xml 2016-01-29 08:23:46.000000000 +0000 @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.33.Final + 4.0.34.Final netty-handler @@ -61,22 +61,10 @@ true - org.mortbay.jetty.npn - npn-boot - provided - true - - org.eclipse.jetty.alpn alpn-api true - - org.mortbay.jetty.alpn - alpn-boot - provided - true - diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNegotiationHandler.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNegotiationHandler.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNegotiationHandler.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/ApplicationProtocolNegotiationHandler.java 2016-01-29 08:23:46.000000000 +0000 @@ -99,8 +99,7 @@ String protocol = sslHandler.applicationProtocol(); configurePipeline(ctx, protocol != null? protocol : fallbackProtocol); } else { - logger.warn("{} TLS handshake failed:", ctx.channel(), handshakeEvent.cause()); - ctx.close(); + handshakeFailure(ctx, handshakeEvent.cause()); } } @@ -117,6 +116,14 @@ */ protected abstract void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception; + /** + * Invoked on failed initial SSL/TLS handshake. + */ + protected void handshakeFailure(ChannelHandlerContext ctx, Throwable cause) throws Exception { + logger.warn("{} TLS handshake failed:", ctx.channel(), cause); + ctx.close(); + } + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.warn("{} Failed to select the application-level protocol:", ctx.channel(), cause); diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java 2016-01-29 08:23:46.000000000 +0000 @@ -191,17 +191,6 @@ "Either both keyCertChainFile and keyFile needs to be null or none of them"); } synchronized (OpenSslContext.class) { - if (trustCertChainFile != null) { - /* Load the certificate chain. We must NOT skip the first cert when client mode */ - if (!SSLContext.setCertificateChainFile(ctx, trustCertChainFile.getPath(), false)) { - long error = SSL.getLastErrorNumber(); - if (OpenSsl.isError(error)) { - throw new SSLException( - "failed to set certificate chain: " - + trustCertChainFile + " (" + SSL.getErrorString(error) + ')'); - } - } - } if (keyCertChainFile != null && keyFile != null) { /* Load the certificate file and private key. */ try { @@ -214,6 +203,16 @@ " (" + SSL.getErrorString(error) + ')'); } } + // We may have more then one cert in the chain so add all of them now. We must NOT skip the + // first cert when client mode. + if (!SSLContext.setCertificateChainFile(ctx, keyCertChainFile.getPath(), false)) { + long error = SSL.getLastErrorNumber(); + if (OpenSsl.isError(error)) { + throw new SSLException( + "failed to set certificate chain: " + + keyCertChainFile + " (" + SSL.getErrorString(error) + ')'); + } + } } catch (SSLException e) { throw e; } catch (Exception e) { @@ -281,28 +280,6 @@ "Either both keyCertChain and key needs to be null or none of them"); } synchronized (OpenSslContext.class) { - if (trustCertChain != null) { - long trustCertChainBio = 0; - - try { - trustCertChainBio = toBIO(trustCertChain); - /* Load the certificate chain. We must NOT skip the first cert when client mode */ - if (!SSLContext.setCertificateChainBio(ctx, trustCertChainBio, false)) { - long error = SSL.getLastErrorNumber(); - if (OpenSsl.isError(error)) { - throw new SSLException( - "failed to set certificate chain: " + SSL.getErrorString(error)); - } - } - } catch (Exception e) { - throw new SSLException( - "failed to set certificate chain", e); - } finally { - if (trustCertChainBio != 0) { - SSL.freeBIO(trustCertChainBio); - } - } - } if (keyCertChain != null && key != null) { /* Load the certificate file and private key. */ long keyBio = 0; @@ -321,6 +298,15 @@ + SSL.getErrorString(error)); } } + // We may have more then one cert in the chain so add all of them now. We must NOT skip the + // first cert when client mode. + if (!SSLContext.setCertificateChainBio(ctx, keyCertChainBio, false)) { + long error = SSL.getLastErrorNumber(); + if (OpenSsl.isError(error)) { + throw new SSLException( + "failed to set certificate chain: " + SSL.getErrorString(error)); + } + } } catch (SSLException e) { throw e; } catch (Exception e) { diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java 2016-01-29 08:23:46.000000000 +0000 @@ -295,10 +295,8 @@ @Override public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { - final OpenSslEngine engine = new OpenSslEngine(ctx, alloc, isClient(), sessionContext(), apn, engineMap, + return new OpenSslEngine(ctx, alloc, isClient(), sessionContext(), apn, engineMap, rejectRemoteInitiatedRenegotiation, peerHost, peerPort, keyCertChain, clientAuth); - engineMap.add(engine); - return engine; } /** @@ -488,7 +486,18 @@ ByteBuf buffer = Unpooled.directBuffer(); try { buffer.writeBytes(BEGIN_PRIVATE_KEY); - buffer.writeBytes(Base64.encode(Unpooled.wrappedBuffer(key.getEncoded()), true)); + ByteBuf wrappedBuf = Unpooled.wrappedBuffer(key.getEncoded()); + final ByteBuf encodedBuf; + try { + encodedBuf = Base64.encode(wrappedBuf, true); + try { + buffer.writeBytes(encodedBuf); + } finally { + encodedBuf.release(); + } + } finally { + wrappedBuf.release(); + } buffer.writeBytes(END_PRIVATE_KEY); return newBIO(buffer); } finally { @@ -508,7 +517,17 @@ try { for (X509Certificate cert: certChain) { buffer.writeBytes(BEGIN_CERT); - buffer.writeBytes(Base64.encode(Unpooled.wrappedBuffer(cert.getEncoded()), true)); + ByteBuf wrappedBuf = Unpooled.wrappedBuffer(cert.getEncoded()); + try { + ByteBuf encodedBuf = Base64.encode(wrappedBuf, true); + try { + buffer.writeBytes(encodedBuf); + } finally { + encodedBuf.release(); + } + } finally { + wrappedBuf.release(); + } buffer.writeBytes(END_CERT); } return newBIO(buffer); diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java 2016-01-29 08:23:46.000000000 +0000 @@ -148,7 +148,6 @@ private HandshakeState handshakeState = HandshakeState.NOT_STARTED; private boolean receivedShutdown; - @SuppressWarnings("UnusedDeclaration") private volatile int destroyed; private volatile ClientAuth clientAuth = ClientAuth.NONE; @@ -168,7 +167,7 @@ private final OpenSslApplicationProtocolNegotiator apn; private final boolean rejectRemoteInitiatedRenegation; private final OpenSslSession session; - private final java.security.cert.Certificate[] localCerts; + private final Certificate[] localCerts; private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1]; private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1]; @@ -202,7 +201,7 @@ boolean clientMode, OpenSslSessionContext sessionContext, OpenSslApplicationProtocolNegotiator apn, OpenSslEngineMap engineMap, boolean rejectRemoteInitiatedRenegation, String peerHost, int peerPort, - java.security.cert.Certificate[] localCerts, + Certificate[] localCerts, ClientAuth clientAuth) { super(peerHost, peerPort); OpenSsl.ensureAvailability(); @@ -212,7 +211,6 @@ this.alloc = checkNotNull(alloc, "alloc"); this.apn = checkNotNull(apn, "apn"); - this.clientAuth = clientMode ? ClientAuth.NONE : checkNotNull(clientAuth, "clientAuth"); ssl = SSL.newSSL(sslCtx, !clientMode); session = new OpenSslSession(sessionContext); networkBIO = SSL.makeNetworkBIO(ssl); @@ -220,16 +218,25 @@ this.engineMap = engineMap; this.rejectRemoteInitiatedRenegation = rejectRemoteInitiatedRenegation; this.localCerts = localCerts; + + // Set the client auth mode, this needs to be done via setClientAuth(...) method so we actually call the + // needed JNI methods. + setClientAuth(clientMode ? ClientAuth.NONE : checkNotNull(clientAuth, "clientAuth")); } @Override - public SSLSession getHandshakeSession() { - if (handshakeState != HandshakeState.NOT_STARTED) { - // handshake started we are able to return the session. + public synchronized SSLSession getHandshakeSession() { + // Javadocs state return value should be: + // null if this instance is not currently handshaking, or if the current handshake has not + // progressed far enough to create a basic SSLSession. Otherwise, this method returns the + // SSLSession currently being negotiated. + switch(handshakeState) { + case NOT_STARTED: + case FINISHED: + return null; + default: return session; } - // As stated by the javadocs of getHandshakeSession() we should return null if the handshake not started yet. - return null; } /** @@ -518,7 +525,7 @@ break; default: // Everything else is considered as error - shutdownWithError("SSL_write"); + throw shutdownWithError("SSL_write"); } } @@ -540,24 +547,15 @@ return newResult(bytesConsumed, bytesProduced, status); } - private void checkPendingHandshakeException() throws SSLHandshakeException { - if (handshakeException != null) { - SSLHandshakeException exception = handshakeException; - handshakeException = null; - shutdown(); - throw exception; - } - } - /** * Log the error, shutdown the engine and throw an exception. */ - private void shutdownWithError(String operations) throws SSLException { + private SSLException shutdownWithError(String operations) { String err = SSL.getLastError(); - shutdownWithError(operations, err); + return shutdownWithError(operations, err); } - private void shutdownWithError(String operation, String err) throws SSLException { + private SSLException shutdownWithError(String operation, String err) { if (logger.isDebugEnabled()) { logger.debug("{} failed: OpenSSL error: {}", operation, err); } @@ -565,9 +563,9 @@ // There was an internal error -- shutdown shutdown(); if (handshakeState == HandshakeState.FINISHED) { - throw new SSLException(err); + return new SSLException(err); } - throw new SSLHandshakeException(err); + return new SSLHandshakeException(err); } public synchronized SSLEngineResult unwrap( @@ -725,8 +723,7 @@ // break to the outer loop return newResult(bytesConsumed, bytesProduced, status); default: - // Everything else is considered as error so shutdown and throw an exceptions - shutdownWithError("SSL_read"); + return sslReadErrorResult(SSL.getLastErrorNumber(), bytesConsumed, bytesProduced); } } } @@ -737,7 +734,7 @@ // We do not check SSL_get_error as we are not interested in any error that is not fatal. int err = SSL.getLastErrorNumber(); if (OpenSsl.isError(err)) { - shutdownWithError("SSL_read", SSL.getErrorString(err)); + return sslReadErrorResult(err, bytesConsumed, bytesProduced); } } } @@ -756,6 +753,24 @@ return newResult(bytesConsumed, bytesProduced, status); } + private SSLEngineResult sslReadErrorResult(int err, int bytesConsumed, int bytesProduced) throws SSLException { + String errStr = SSL.getErrorString(err); + + // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the + // BIO first or can just shutdown and throw it now. + // This is needed so we ensure close_notify etc is correctly send to the remote peer. + // See https://github.com/netty/netty/issues/3900 + if (SSL.pendingWrittenBytesInBIO(networkBIO) > 0) { + if (handshakeException == null && handshakeState != HandshakeState.FINISHED) { + // we seems to have data left that needs to be transfered and so the user needs + // call wrap(...). Store the error so we can pick it up later. + handshakeException = new SSLHandshakeException(errStr); + } + return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced); + } + throw shutdownWithError("SSL_read", errStr); + } + private int pendingAppData() { // There won't be any application data until we're done handshaking. // We first check handshakeFinished to eliminate the overhead of extra JNI call if possible. @@ -1065,21 +1080,29 @@ // Enable all and then disable what we not want SSL.setOptions(ssl, SSL.SSL_OP_ALL); + // Clear out options which disable protocols + SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 | + SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2); + + int opts = 0; if (!sslv2) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_SSLv2); + opts |= SSL.SSL_OP_NO_SSLv2; } if (!sslv3) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_SSLv3); + opts |= SSL.SSL_OP_NO_SSLv3; } if (!tlsv1) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1); + opts |= SSL.SSL_OP_NO_TLSv1; } if (!tlsv1_1) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_1); + opts |= SSL.SSL_OP_NO_TLSv1_1; } if (!tlsv1_2) { - SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_2); + opts |= SSL.SSL_OP_NO_TLSv1_2; } + + // Disable protocols we do not want + SSL.setOptions(ssl, opts); } else { throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols)); } @@ -1128,7 +1151,7 @@ // https://github.com/apache/httpd/blob/2.4.16/modules/ssl/ssl_engine_kernel.c#L812 // http://h71000.www7.hp.com/doc/83final/ba554_90007/ch04s03.html if (SSL.renegotiate(ssl) != 1 || SSL.doHandshake(ssl) != 1) { - shutdownWithError("renegotiation failed"); + throw shutdownWithError("renegotiation failed"); } SSL.setState(ssl, SSL.SSL_ST_ACCEPT); @@ -1158,9 +1181,37 @@ return FINISHED; } checkEngineClosed(); + + // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the + // BIO first or can just shutdown and throw it now. + // This is needed so we ensure close_notify etc is correctly send to the remote peer. + // See https://github.com/netty/netty/issues/3900 + SSLHandshakeException exception = handshakeException; + if (exception != null) { + if (SSL.pendingWrittenBytesInBIO(networkBIO) > 0) { + // There is something pending, we need to consume it first via a WRAP so we not loose anything. + return NEED_WRAP; + } + // No more data left to send to the remote peer, so null out the exception field, shutdown and throw + // the exception. + handshakeException = null; + shutdown(); + throw exception; + } + + // Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier. + engineMap.add(this); + int code = SSL.doHandshake(ssl); if (code <= 0) { - checkPendingHandshakeException(); + // Check if we have a pending exception that was created during the handshake and if so throw it after + // shutdown the connection. + if (handshakeException != null) { + exception = handshakeException; + handshakeException = null; + shutdown(); + throw exception; + } int sslError = SSL.getError(ssl, code); @@ -1170,13 +1221,11 @@ return pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO)); default: // Everything else is considered as error - shutdownWithError("SSL_do_handshake"); + throw shutdownWithError("SSL_do_handshake"); } } - // if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished. session.handshakeFinished(); - return FINISHED; } @@ -1205,18 +1254,12 @@ @Override public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { // Check if we are in the initial handshake phase or shutdown phase - if (needPendingStatus()) { - return pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO)); - } - return NOT_HANDSHAKING; + return needPendingStatus() ? pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO)) : NOT_HANDSHAKING; } private SSLEngineResult.HandshakeStatus getHandshakeStatus(int pending) { // Check if we are in the initial handshake phase or shutdown phase - if (needPendingStatus()) { - return pendingStatus(pending); - } - return NOT_HANDSHAKING; + return needPendingStatus() ? pendingStatus(pending) : NOT_HANDSHAKING; } private boolean needPendingStatus() { diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java 2016-01-29 08:23:46.000000000 +0000 @@ -321,6 +321,7 @@ ClientAuth.NONE); OpenSsl.ensureAvailability(); + checkKeyManagerFactory(keyManagerFactory); checkNotNull(keyCertChainFile, "keyCertChainFile"); if (!keyCertChainFile.isFile()) { throw new IllegalArgumentException("keyCertChainFile is not a file: " + keyCertChainFile); @@ -420,6 +421,7 @@ clientAuth); OpenSsl.ensureAvailability(); + checkKeyManagerFactory(keyManagerFactory); checkNotNull(keyCertChain, "keyCertChainFile"); checkNotNull(key, "keyFile"); @@ -528,4 +530,11 @@ public OpenSslServerSessionContext sessionContext() { return sessionContext; } + + private static void checkKeyManagerFactory(KeyManagerFactory keyManagerFactory) { + if (keyManagerFactory != null) { + throw new IllegalArgumentException( + "KeyManagerFactory is currently not supported with OpenSslServerContext"); + } + } } diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SniHandler.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SniHandler.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SniHandler.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SniHandler.java 2016-01-29 08:23:46.000000000 +0000 @@ -37,14 +37,19 @@ */ public class SniHandler extends ByteToMessageDecoder { + // Maximal number of ssl records to inspect before fallback to the default SslContext. + private static final int MAX_SSL_RECORDS = 4; + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SniHandler.class); + private static final Selection EMPTY_SELECTION = new Selection(null, null); + private final DomainNameMapping mapping; - private boolean handshaken; - private volatile String hostname; - private volatile SslContext selectedContext; + private boolean handshakeFailed; + + private volatile Selection selection = EMPTY_SELECTION; /** * Create a SNI detection handler with configured {@link SslContext} @@ -59,127 +64,200 @@ } this.mapping = (DomainNameMapping) mapping; - handshaken = false; } /** * @return the selected hostname */ public String hostname() { - return hostname; + return selection.hostname; } /** * @return the selected sslcontext */ public SslContext sslContext() { - return selectedContext; + return selection.context; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - if (!handshaken && in.readableBytes() >= 5) { - String hostname = sniHostNameFromHandshakeInfo(in); - if (hostname != null) { - hostname = IDN.toASCII(hostname, IDN.ALLOW_UNASSIGNED).toLowerCase(Locale.US); - } - this.hostname = hostname; - - // the mapping will return default context when this.hostname is null - selectedContext = mapping.map(hostname); - } - - if (handshaken) { - SslHandler sslHandler = selectedContext.newHandler(ctx.alloc()); - ctx.pipeline().replace(this, SslHandler.class.getName(), sslHandler); - } - } - - private String sniHostNameFromHandshakeInfo(ByteBuf in) { - int readerIndex = in.readerIndex(); - try { - int command = in.getUnsignedByte(readerIndex); - - // tls, but not handshake command - switch (command) { - case SslConstants.SSL_CONTENT_TYPE_CHANGE_CIPHER_SPEC: - case SslConstants.SSL_CONTENT_TYPE_ALERT: - case SslConstants.SSL_CONTENT_TYPE_APPLICATION_DATA: - return null; - case SslConstants.SSL_CONTENT_TYPE_HANDSHAKE: - break; - default: - //not tls or sslv3, do not try sni - handshaken = true; - return null; - } - - int majorVersion = in.getUnsignedByte(readerIndex + 1); + if (!handshakeFailed) { + final int writerIndex = in.writerIndex(); + try { + loop: + for (int i = 0; i < MAX_SSL_RECORDS; i++) { + final int readerIndex = in.readerIndex(); + final int readableBytes = writerIndex - readerIndex; + if (readableBytes < SslUtils.SSL_RECORD_HEADER_LENGTH) { + // Not enough data to determine the record type and length. + return; + } - // SSLv3 or TLS - if (majorVersion == 3) { + final int command = in.getUnsignedByte(readerIndex); - int packetLength = in.getUnsignedShort(readerIndex + 3) + 5; + // tls, but not handshake command + switch (command) { + case SslUtils.SSL_CONTENT_TYPE_CHANGE_CIPHER_SPEC: + case SslUtils.SSL_CONTENT_TYPE_ALERT: + final int len = SslUtils.getEncryptedPacketLength(in, readerIndex); + + // Not an SSL/TLS packet + if (len == -1) { + handshakeFailed = true; + NotSslRecordException e = new NotSslRecordException( + "not an SSL/TLS record: " + ByteBufUtil.hexDump(in)); + in.skipBytes(in.readableBytes()); + ctx.fireExceptionCaught(e); - if (in.readableBytes() >= packetLength) { - // decode the ssl client hello packet - // we have to skip some var-length fields - int offset = readerIndex + 43; - - int sessionIdLength = in.getUnsignedByte(offset); - offset += sessionIdLength + 1; - - int cipherSuitesLength = in.getUnsignedShort(offset); - offset += cipherSuitesLength + 2; - - int compressionMethodLength = in.getUnsignedByte(offset); - offset += compressionMethodLength + 1; - - int extensionsLength = in.getUnsignedShort(offset); - offset += 2; - int extensionsLimit = offset + extensionsLength; - - while (offset < extensionsLimit) { - int extensionType = in.getUnsignedShort(offset); - offset += 2; - - int extensionLength = in.getUnsignedShort(offset); - offset += 2; - - // SNI - if (extensionType == 0) { - handshaken = true; - int serverNameType = in.getUnsignedByte(offset + 2); - if (serverNameType == 0) { - int serverNameLength = in.getUnsignedShort(offset + 3); - return in.toString(offset + 5, serverNameLength, - CharsetUtil.UTF_8); - } else { - // invalid enum value - return null; + SslUtils.notifyHandshakeFailure(ctx, e); + return; + } + if (writerIndex - readerIndex - SslUtils.SSL_RECORD_HEADER_LENGTH < len) { + // Not enough data + return; } - } + // increase readerIndex and try again. + in.skipBytes(len); + continue; + case SslUtils.SSL_CONTENT_TYPE_HANDSHAKE: + final int majorVersion = in.getUnsignedByte(readerIndex + 1); + + // SSLv3 or TLS + if (majorVersion == 3) { + final int packetLength = in.getUnsignedShort(readerIndex + 3) + + SslUtils.SSL_RECORD_HEADER_LENGTH; + + if (readableBytes < packetLength) { + // client hello incomplete; try again to decode once more data is ready. + return; + } + + // See https://tools.ietf.org/html/rfc5246#section-7.4.1.2 + // + // Decode the ssl client hello packet. + // We have to skip bytes until SessionID (which sum to 43 bytes). + // + // struct { + // ProtocolVersion client_version; + // Random random; + // SessionID session_id; + // CipherSuite cipher_suites<2..2^16-2>; + // CompressionMethod compression_methods<1..2^8-1>; + // select (extensions_present) { + // case false: + // struct {}; + // case true: + // Extension extensions<0..2^16-1>; + // }; + // } ClientHello; + // + + final int endOffset = readerIndex + packetLength; + int offset = readerIndex + 43; + + if (endOffset - offset < 6) { + break loop; + } + + final int sessionIdLength = in.getUnsignedByte(offset); + offset += sessionIdLength + 1; + + final int cipherSuitesLength = in.getUnsignedShort(offset); + offset += cipherSuitesLength + 2; + + final int compressionMethodLength = in.getUnsignedByte(offset); + offset += compressionMethodLength + 1; + + final int extensionsLength = in.getUnsignedShort(offset); + offset += 2; + final int extensionsLimit = offset + extensionsLength; + + if (extensionsLimit > endOffset) { + // Extensions should never exceed the record boundary. + break loop; + } + + for (;;) { + if (extensionsLimit - offset < 4) { + break loop; + } + + final int extensionType = in.getUnsignedShort(offset); + offset += 2; + + final int extensionLength = in.getUnsignedShort(offset); + offset += 2; + + if (extensionsLimit - offset < extensionLength) { + break loop; + } + + // SNI + // See https://tools.ietf.org/html/rfc6066#page-6 + if (extensionType == 0) { + offset += 2; + if (extensionsLimit - offset < 3) { + break loop; + } + + final int serverNameType = in.getUnsignedByte(offset); + offset++; + + if (serverNameType == 0) { + final int serverNameLength = in.getUnsignedShort(offset); + offset += 2; + + if (extensionsLimit - offset < serverNameLength) { + break loop; + } + + final String hostname = in.toString(offset, serverNameLength, + CharsetUtil.UTF_8); + + select(ctx, IDN.toASCII(hostname, + IDN.ALLOW_UNASSIGNED).toLowerCase(Locale.US)); + return; + } else { + // invalid enum value + break loop; + } + } - offset += extensionLength; + offset += extensionLength; + } + } + // Fall-through + default: + //not tls, ssl or application data, do not try sni + break loop; } - - handshaken = true; - return null; - } else { - // client hello incomplete - return null; } - } else { - handshaken = true; - return null; - } - } catch (Throwable e) { - // unexpected encoding, ignore sni and use default - if (logger.isDebugEnabled()) { - logger.debug("Unexpected client hello packet: " + ByteBufUtil.hexDump(in), e); + } catch (Throwable e) { + // unexpected encoding, ignore sni and use default + if (logger.isDebugEnabled()) { + logger.debug("Unexpected client hello packet: " + ByteBufUtil.hexDump(in), e); + } } - handshaken = true; - return null; + // Just select the default SslContext + select(ctx, null); + } + } + + private void select(ChannelHandlerContext ctx, String hostname) { + SslContext selectedContext = mapping.map(hostname); + selection = new Selection(selectedContext, hostname); + SslHandler sslHandler = selectedContext.newHandler(ctx.alloc()); + ctx.pipeline().replace(this, SslHandler.class.getName(), sslHandler); + } + + private static final class Selection { + final SslContext context; + final String hostname; + + Selection(SslContext context, String hostname) { + this.context = context; + this.hostname = hostname; } } } diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SslConstants.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SslConstants.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SslConstants.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SslConstants.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.handler.ssl; - -/** - * Constants for SSL packets. - */ -final class SslConstants { - - /** - * change cipher spec - */ - public static final int SSL_CONTENT_TYPE_CHANGE_CIPHER_SPEC = 20; - - /** - * alert - */ - public static final int SSL_CONTENT_TYPE_ALERT = 21; - - /** - * handshake - */ - public static final int SSL_CONTENT_TYPE_HANDSHAKE = 22; - - /** - * application data - */ - public static final int SSL_CONTENT_TYPE_APPLICATION_DATA = 23; - - private SslConstants() { - } -} diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SslContext.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SslContext.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SslContext.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SslContext.java 2016-01-29 08:23:46.000000000 +0000 @@ -902,17 +902,6 @@ return ks; } - static KeyStore buildKeyStore(File certChainFile, File keyFile, String keyPassword) - throws KeyStoreException, NoSuchAlgorithmException, - CertificateException, NoSuchPaddingException, InvalidKeySpecException, - InvalidAlgorithmParameterException, KeyException, IOException { - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(null, null); - ks.setKeyEntry("key", toPrivateKey(keyFile, keyPassword), - keyPassword == null ? null : keyPassword.toCharArray(), toX509Certificates(certChainFile)); - return ks; - } - static PrivateKey toPrivateKey(File keyFile, String keyPassword) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException, diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SslHandler.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SslHandler.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SslHandler.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SslHandler.java 2016-01-29 08:23:46.000000000 +0000 @@ -66,6 +66,8 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import static io.netty.handler.ssl.SslUtils.getEncryptedPacketLength; + /** * Adds SSL * · TLS and StartTLS support to a {@link Channel}. Please refer @@ -393,10 +395,10 @@ */ public ChannelFuture close(final ChannelPromise future) { final ChannelHandlerContext ctx = this.ctx; - ctx.executor().execute(new Runnable() { + ctx.executor().execute(new OneTimeTask() { @Override public void run() { - SslHandler.this.outboundClosed = true; + outboundClosed = true; engine.closeOutbound(); try { write(ctx, Unpooled.EMPTY_BUFFER, future); @@ -429,6 +431,10 @@ // Check if queue is not empty first because create a new ChannelException is expensive pendingUnencryptedWrites.removeAndFailAll(new ChannelException("Pending write on removal of SslHandler")); } + if (engine instanceof OpenSslEngine) { + // Call shutdown so we ensure all the native memory is released asap + ((OpenSslEngine) engine).shutdown(); + } } @Override @@ -497,8 +503,13 @@ if (!handshakePromise.isDone()) { flushedBeforeHandshake = true; } - wrap(ctx, false); - ctx.flush(); + try { + wrap(ctx, false); + } finally { + // We may have written some parts of data before an exception was thrown so ensure we always flush. + // See https://github.com/netty/netty/issues/3900#issuecomment-172481830 + ctx.flush(); + } } private void wrap(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException { @@ -636,6 +647,10 @@ } } catch (SSLException e) { setHandshakeFailure(ctx, e); + + // We may have written some parts of data before an exception was thrown so ensure we always flush. + // See https://github.com/netty/netty/issues/3900#issuecomment-172481830 + flushIfNeeded(ctx); throw e; } finally { if (out != null) { @@ -809,84 +824,13 @@ * Is thrown if the given {@link ByteBuf} has not at least 5 bytes to read. */ public static boolean isEncrypted(ByteBuf buffer) { - if (buffer.readableBytes() < 5) { - throw new IllegalArgumentException("buffer must have at least 5 readable bytes"); + if (buffer.readableBytes() < SslUtils.SSL_RECORD_HEADER_LENGTH) { + throw new IllegalArgumentException( + "buffer must have at least " + SslUtils.SSL_RECORD_HEADER_LENGTH + " readable bytes"); } return getEncryptedPacketLength(buffer, buffer.readerIndex()) != -1; } - /** - * Return how much bytes can be read out of the encrypted data. Be aware that this method will not increase - * the readerIndex of the given {@link ByteBuf}. - * - * @param buffer - * The {@link ByteBuf} to read from. Be aware that it must have at least 5 bytes to read, - * otherwise it will throw an {@link IllegalArgumentException}. - * @return length - * The length of the encrypted packet that is included in the buffer. This will - * return {@code -1} if the given {@link ByteBuf} is not encrypted at all. - * @throws IllegalArgumentException - * Is thrown if the given {@link ByteBuf} has not at least 5 bytes to read. - */ - private static int getEncryptedPacketLength(ByteBuf buffer, int offset) { - int packetLength = 0; - - // SSLv3 or TLS - Check ContentType - boolean tls; - switch (buffer.getUnsignedByte(offset)) { - case 20: // change_cipher_spec - case 21: // alert - case 22: // handshake - case 23: // application_data - tls = true; - break; - default: - // SSLv2 or bad data - tls = false; - } - - if (tls) { - // SSLv3 or TLS - Check ProtocolVersion - int majorVersion = buffer.getUnsignedByte(offset + 1); - if (majorVersion == 3) { - // SSLv3 or TLS - packetLength = buffer.getUnsignedShort(offset + 3) + 5; - if (packetLength <= 5) { - // Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data) - tls = false; - } - } else { - // Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data) - tls = false; - } - } - - if (!tls) { - // SSLv2 or bad data - Check the version - boolean sslv2 = true; - int headerLength = (buffer.getUnsignedByte(offset) & 0x80) != 0 ? 2 : 3; - int majorVersion = buffer.getUnsignedByte(offset + headerLength + 1); - if (majorVersion == 2 || majorVersion == 3) { - // SSLv2 - if (headerLength == 2) { - packetLength = (buffer.getShort(offset) & 0x7FFF) + 2; - } else { - packetLength = (buffer.getShort(offset) & 0x3FFF) + 3; - } - if (packetLength <= headerLength) { - sslv2 = false; - } - } else { - sslv2 = false; - } - - if (!sslv2) { - return -1; - } - } - return packetLength; - } - @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws SSLException { final int startOffset = in.readerIndex(); @@ -909,7 +853,7 @@ while (totalLength < OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) { final int readableBytes = endOffset - offset; - if (readableBytes < 5) { + if (readableBytes < SslUtils.SSL_RECORD_HEADER_LENGTH) { break; } @@ -990,10 +934,7 @@ // Discard bytes of the cumulation buffer if needed. discardSomeReadBytes(); - if (needsFlush) { - needsFlush = false; - ctx.flush(); - } + flushIfNeeded(ctx); // If handshake is not finished yet, we need more data. if (!ctx.channel().config().isAutoRead() && (!firedChannelRead || !handshakePromise.isDone())) { @@ -1006,6 +947,13 @@ ctx.fireChannelReadComplete(); } + private void flushIfNeeded(ChannelHandlerContext ctx) { + if (needsFlush) { + needsFlush = false; + ctx.flush(); + } + } + /** * Calls {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} with an empty buffer to handle handshakes, etc. */ @@ -1069,7 +1017,19 @@ case FINISHED: setHandshakeSuccess(); wrapLater = true; - continue; + + // We 'break' here and NOT 'continue' as android API version 21 has a bug where they consume + // data from the buffer but NOT correctly set the SSLEngineResult.bytesConsumed(). + // Because of this it will raise an exception on the next iteration of the for loop on android + // API version 21. Just doing a break will work here as produced and consumed will both be 0 + // and so we break out of the complete for (;;) loop and so call decode(...) again later on. + // On other platforms this will have no negative effect as we will just continue with the + // for (;;) loop if something was either consumed or produced. + // + // See: + // - https://github.com/netty/netty/issues/4116 + // - https://code.google.com/p/android/issues/detail?id=198639&thanks=198639&ts=1452501203 + break; case NOT_HANDSHAKING: if (setHandshakeSuccessIfStillHandshaking()) { wrapLater = true; @@ -1179,7 +1139,7 @@ } final CountDownLatch latch = new CountDownLatch(1); - delegatedTaskExecutor.execute(new Runnable() { + delegatedTaskExecutor.execute(new OneTimeTask() { @Override public void run() { try { @@ -1283,8 +1243,7 @@ private void notifyHandshakeFailure(Throwable cause) { if (handshakePromise.tryFailure(cause)) { - ctx.fireUserEventTriggered(new SslHandshakeCompletionEvent(cause)); - ctx.close(); + SslUtils.notifyHandshakeFailure(ctx, cause); } } @@ -1392,6 +1351,10 @@ } handshakePromise = p = newHandshakePromise; + } else if (engine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) { + // Not all SSLEngine implementations support calling beginHandshake multiple times while a handshake + // is in progress. See https://github.com/netty/netty/issues/4718. + return; } else { // Forced to reuse the old handshake. p = handshakePromise; @@ -1403,9 +1366,12 @@ try { engine.beginHandshake(); wrapNonAppData(ctx, false); - ctx.flush(); } catch (Exception e) { notifyHandshakeFailure(e); + } finally { + // We have may haven written some parts of data before an exception was thrown so ensure we always flush. + // See https://github.com/netty/netty/issues/3900#issuecomment-172481830 + ctx.flush(); } // Set timeout if necessary. @@ -1414,7 +1380,7 @@ return; } - final ScheduledFuture timeoutFuture = ctx.executor().schedule(new Runnable() { + final ScheduledFuture timeoutFuture = ctx.executor().schedule(new OneTimeTask() { @Override public void run() { if (p.isDone()) { @@ -1456,7 +1422,7 @@ final ScheduledFuture timeoutFuture; if (closeNotifyTimeoutMillis > 0) { // Force-close the connection if close_notify is not fully sent in time. - timeoutFuture = ctx.executor().schedule(new Runnable() { + timeoutFuture = ctx.executor().schedule(new OneTimeTask() { @Override public void run() { logger.warn("{} Last write attempt timed out; force-closing the connection.", ctx.channel()); diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SslUtils.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SslUtils.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/SslUtils.java 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/SslUtils.java 2016-01-29 08:23:46.000000000 +0000 @@ -0,0 +1,127 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.ssl; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; + +/** + * Constants for SSL packets. + */ +final class SslUtils { + + /** + * change cipher spec + */ + public static final int SSL_CONTENT_TYPE_CHANGE_CIPHER_SPEC = 20; + + /** + * alert + */ + public static final int SSL_CONTENT_TYPE_ALERT = 21; + + /** + * handshake + */ + public static final int SSL_CONTENT_TYPE_HANDSHAKE = 22; + + /** + * application data + */ + public static final int SSL_CONTENT_TYPE_APPLICATION_DATA = 23; + + /** + * the length of the ssl record header (in bytes) + */ + public static final int SSL_RECORD_HEADER_LENGTH = 5; + + /** + * Return how much bytes can be read out of the encrypted data. Be aware that this method will not increase + * the readerIndex of the given {@link ByteBuf}. + * + * @param buffer + * The {@link ByteBuf} to read from. Be aware that it must have at least + * {@link #SSL_RECORD_HEADER_LENGTH} bytes to read, + * otherwise it will throw an {@link IllegalArgumentException}. + * @return length + * The length of the encrypted packet that is included in the buffer. This will + * return {@code -1} if the given {@link ByteBuf} is not encrypted at all. + * @throws IllegalArgumentException + * Is thrown if the given {@link ByteBuf} has not at least {@link #SSL_RECORD_HEADER_LENGTH} + * bytes to read. + */ + static int getEncryptedPacketLength(ByteBuf buffer, int offset) { + int packetLength = 0; + + // SSLv3 or TLS - Check ContentType + boolean tls; + switch (buffer.getUnsignedByte(offset)) { + case SSL_CONTENT_TYPE_CHANGE_CIPHER_SPEC: + case SSL_CONTENT_TYPE_ALERT: + case SSL_CONTENT_TYPE_HANDSHAKE: + case SSL_CONTENT_TYPE_APPLICATION_DATA: + tls = true; + break; + default: + // SSLv2 or bad data + tls = false; + } + + if (tls) { + // SSLv3 or TLS - Check ProtocolVersion + int majorVersion = buffer.getUnsignedByte(offset + 1); + if (majorVersion == 3) { + // SSLv3 or TLS + packetLength = buffer.getUnsignedShort(offset + 3) + SSL_RECORD_HEADER_LENGTH; + if (packetLength <= SSL_RECORD_HEADER_LENGTH) { + // Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data) + tls = false; + } + } else { + // Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data) + tls = false; + } + } + + if (!tls) { + // SSLv2 or bad data - Check the version + int headerLength = (buffer.getUnsignedByte(offset) & 0x80) != 0 ? 2 : 3; + int majorVersion = buffer.getUnsignedByte(offset + headerLength + 1); + if (majorVersion == 2 || majorVersion == 3) { + // SSLv2 + if (headerLength == 2) { + packetLength = (buffer.getShort(offset) & 0x7FFF) + 2; + } else { + packetLength = (buffer.getShort(offset) & 0x3FFF) + 3; + } + if (packetLength <= headerLength) { + return -1; + } + } else { + return -1; + } + } + return packetLength; + } + + static void notifyHandshakeFailure(ChannelHandlerContext ctx, Throwable cause) { + ctx.fireUserEventTriggered(new SslHandshakeCompletionEvent(cause)); + ctx.close(); + } + + private SslUtils() { + } +} diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java 2016-01-29 08:23:46.000000000 +0000 @@ -16,6 +16,7 @@ package io.netty.handler.ssl.util; +import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.base64.Base64; import io.netty.util.CharsetUtil; @@ -162,11 +163,20 @@ certificate = new File(paths[0]); privateKey = new File(paths[1]); key = keypair.getPrivate(); + FileInputStream certificateInput = null; try { - cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate( - new FileInputStream(certificate)); + certificateInput = new FileInputStream(certificate); + cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(certificateInput); } catch (Exception e) { throw new CertificateEncodingException(e); + } finally { + if (certificateInput != null) { + try { + certificateInput.close(); + } catch (IOException e) { + logger.warn("Failed to close a file: " + certificate, e); + } + } } } @@ -208,11 +218,22 @@ static String[] newSelfSignedCertificate( String fqdn, PrivateKey key, X509Certificate cert) throws IOException, CertificateEncodingException { - // Encode the private key into a file. - String keyText = "-----BEGIN PRIVATE KEY-----\n" + - Base64.encode(Unpooled.wrappedBuffer(key.getEncoded()), true).toString(CharsetUtil.US_ASCII) + - "\n-----END PRIVATE KEY-----\n"; + ByteBuf wrappedBuf = Unpooled.wrappedBuffer(key.getEncoded()); + ByteBuf encodedBuf; + final String keyText; + try { + encodedBuf = Base64.encode(wrappedBuf, true); + try { + keyText = "-----BEGIN PRIVATE KEY-----\n" + + encodedBuf.toString(CharsetUtil.US_ASCII) + + "\n-----END PRIVATE KEY-----\n"; + } finally { + encodedBuf.release(); + } + } finally { + wrappedBuf.release(); + } File keyFile = File.createTempFile("keyutil_" + fqdn + '_', ".key"); keyFile.deleteOnExit(); @@ -229,10 +250,21 @@ } } - // Encode the certificate into a CRT file. - String certText = "-----BEGIN CERTIFICATE-----\n" + - Base64.encode(Unpooled.wrappedBuffer(cert.getEncoded()), true).toString(CharsetUtil.US_ASCII) + - "\n-----END CERTIFICATE-----\n"; + wrappedBuf = Unpooled.wrappedBuffer(cert.getEncoded()); + final String certText; + try { + encodedBuf = Base64.encode(wrappedBuf, true); + try { + // Encode the certificate into a CRT file. + certText = "-----BEGIN CERTIFICATE-----\n" + + encodedBuf.toString(CharsetUtil.US_ASCII) + + "\n-----END CERTIFICATE-----\n"; + } finally { + encodedBuf.release(); + } + } finally { + wrappedBuf.release(); + } File certFile = File.createTempFile("keyutil_" + fqdn + '_', ".crt"); certFile.deleteOnExit(); diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java netty-4.0.34/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java 2016-01-29 08:23:46.000000000 +0000 @@ -28,6 +28,7 @@ import io.netty.channel.ChannelProgressivePromise; import io.netty.channel.ChannelPromise; import io.netty.util.ReferenceCountUtil; +import io.netty.util.internal.OneTimeTask; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -113,7 +114,7 @@ } } else { // let the transfer resume on the next event loop round - ctx.executor().execute(new Runnable() { + ctx.executor().execute(new OneTimeTask() { @Override public void run() { diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/timeout/WriteTimeoutHandler.java netty-4.0.34/handler/src/main/java/io/netty/handler/timeout/WriteTimeoutHandler.java --- netty-4.0.33/handler/src/main/java/io/netty/handler/timeout/WriteTimeoutHandler.java 2015-11-03 13:18:17.000000000 +0000 +++ netty-4.0.34/handler/src/main/java/io/netty/handler/timeout/WriteTimeoutHandler.java 2016-01-29 08:23:46.000000000 +0000 @@ -24,17 +24,16 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; +import io.netty.util.internal.OneTimeTask; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** - * Raises a {@link WriteTimeoutException} when no data was written within a - * certain period of time. + * Raises a {@link WriteTimeoutException} when a write operation cannot finish in a certain period of time. * *
- * // The connection is closed when there is no outbound traffic
- * // for 30 seconds.
+ * // The connection is closed when a write operation cannot finish in 30 seconds.
  *
  * public class MyChannelInitializer extends {@link ChannelInitializer}<{@link Channel}> {
  *     public void initChannel({@link Channel} channel) {
@@ -69,6 +68,11 @@
 
     private final long timeoutNanos;
 
+    /**
+     * A doubly-linked list to track all WriteTimeoutTasks
+     */
+    private WriteTimeoutTask lastTask;
+
     private boolean closed;
 
     /**
@@ -107,33 +111,62 @@
         ctx.write(msg, promise);
     }
 
-    private void scheduleTimeout(final ChannelHandlerContext ctx, final ChannelPromise future) {
-        if (timeoutNanos > 0) {
-            // Schedule a timeout.
-            final ScheduledFuture sf = ctx.executor().schedule(new Runnable() {
-                @Override
-                public void run() {
-                    // Was not written yet so issue a write timeout
-                    // The future itself will be failed with a ClosedChannelException once the close() was issued
-                    // See https://github.com/netty/netty/issues/2159
-                    if (!future.isDone()) {
-                        try {
-                            writeTimedOut(ctx);
-                        } catch (Throwable t) {
-                            ctx.fireExceptionCaught(t);
-                        }
-                    }
-                }
-            }, timeoutNanos, TimeUnit.NANOSECONDS);
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        WriteTimeoutTask task = lastTask;
+        lastTask = null;
+        while (task != null) {
+            task.scheduledFuture.cancel(false);
+            WriteTimeoutTask prev = task.prev;
+            task.prev = null;
+            task.next = null;
+            task = prev;
+        }
+    }
 
-            // Cancel the scheduled timeout if the flush future is complete.
-            future.addListener(new ChannelFutureListener() {
-                @Override
-                public void operationComplete(ChannelFuture future) throws Exception {
-                    sf.cancel(false);
-                }
-            });
+    private void scheduleTimeout(final ChannelHandlerContext ctx, final ChannelPromise promise) {
+        // Schedule a timeout.
+        final WriteTimeoutTask task = new WriteTimeoutTask(ctx, promise);
+        task.scheduledFuture = ctx.executor().schedule(task, timeoutNanos, TimeUnit.NANOSECONDS);
+
+        if (!task.scheduledFuture.isDone()) {
+            addWriteTimeoutTask(task);
+
+            // Cancel the scheduled timeout if the flush promise is complete.
+            promise.addListener(task);
+        }
+    }
+
+    private void addWriteTimeoutTask(WriteTimeoutTask task) {
+        if (lastTask == null) {
+            lastTask = task;
+        } else {
+            lastTask.next = task;
+            task.prev = lastTask;
+            lastTask = task;
+        }
+    }
+
+    private void removeWriteTimeoutTask(WriteTimeoutTask task) {
+        if (task == lastTask) {
+            // task is the tail of list
+            assert task.next == null;
+            lastTask = lastTask.prev;
+            if (lastTask != null) {
+                lastTask.next = null;
+            }
+        } else if (task.prev == null && task.next == null) {
+            // Since task is not lastTask, then it has been removed or not been added.
+            return;
+        } else if (task.prev == null) {
+            // task is the head of list and the list has at least 2 nodes
+            task.next.prev = null;
+        } else {
+            task.prev.next = task.next;
+            task.next.prev = task.prev;
         }
+        task.prev = null;
+        task.next = null;
     }
 
     /**
@@ -146,4 +179,43 @@
             closed = true;
         }
     }
+
+    private final class WriteTimeoutTask extends OneTimeTask implements ChannelFutureListener {
+
+        private final ChannelHandlerContext ctx;
+        private final ChannelPromise promise;
+
+        // WriteTimeoutTask is also a node of a doubly-linked list
+        WriteTimeoutTask prev;
+        WriteTimeoutTask next;
+
+        ScheduledFuture scheduledFuture;
+
+        WriteTimeoutTask(ChannelHandlerContext ctx, ChannelPromise promise) {
+            this.ctx = ctx;
+            this.promise = promise;
+        }
+
+        @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 operationComplete(ChannelFuture future) throws Exception {
+            // scheduledFuture has already be set when reaching here
+            scheduledFuture.cancel(false);
+            removeWriteTimeoutTask(this);
+        }
+    }
 }
diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java netty-4.0.34/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java
--- netty-4.0.33/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/handler/src/main/java/io/netty/handler/traffic/ChannelTrafficShapingHandler.java	2016-01-29 08:23:46.000000000 +0000
@@ -18,6 +18,7 @@
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelPromise;
+import io.netty.util.internal.OneTimeTask;
 
 import java.util.ArrayDeque;
 import java.util.concurrent.TimeUnit;
@@ -192,7 +193,7 @@
             checkWriteSuspend(ctx, delay, queueSize);
         }
         final long futureNow = newToSend.relativeTimeAction;
-        ctx.executor().schedule(new Runnable() {
+        ctx.executor().schedule(new OneTimeTask() {
             @Override
             public void run() {
                 sendAllValid(ctx, futureNow);
diff -Nru netty-4.0.33/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java netty-4.0.34/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java
--- netty-4.0.33/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/handler/src/main/java/io/netty/handler/traffic/GlobalTrafficShapingHandler.java	2016-01-29 08:23:46.000000000 +0000
@@ -21,6 +21,7 @@
 import io.netty.channel.ChannelPromise;
 import io.netty.channel.ChannelHandler.Sharable;
 import io.netty.util.concurrent.EventExecutor;
+import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.PlatformDependent;
 
 import java.util.ArrayDeque;
@@ -360,7 +361,7 @@
         }
         final long futureNow = newToSend.relativeTimeAction;
         final PerChannel forSchedule = perChannel;
-        ctx.executor().schedule(new Runnable() {
+        ctx.executor().schedule(new OneTimeTask() {
             @Override
             public void run() {
                 sendAllValid(ctx, forSchedule, futureNow);
diff -Nru netty-4.0.33/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java netty-4.0.34/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java
--- netty-4.0.33/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -15,37 +15,24 @@
  */
 package io.netty.handler.ssl;
 
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeNoException;
-import io.netty.bootstrap.Bootstrap;
-import io.netty.bootstrap.ServerBootstrap;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelHandlerAdapter;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.nio.NioServerSocketChannel;
-import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
+import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
+import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
 import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector;
 import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectorFactory;
 import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
 import io.netty.handler.ssl.util.SelfSignedCertificate;
-import io.netty.util.NetUtil;
+import org.junit.Test;
 
-import java.net.InetSocketAddress;
-import java.security.cert.CertificateException;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLHandshakeException;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLHandshakeException;
-
-import org.junit.Test;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
 
 public class JdkSslEngineTest extends SSLEngineTest {
     private static final String PREFERRED_APPLICATION_LEVEL_PROTOCOL = "my-protocol-http2";
@@ -59,13 +46,13 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkNpnSslEngine.isAvailable()) {
-                throw new RuntimeException("NPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.NPN);
             }
-            JdkApplicationProtocolNegotiator apn = new JdkNpnApplicationProtocolNegotiator(true, true,
+            ApplicationProtocolConfig apn = failingNegotiator(Protocol.NPN,
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-            mySetup(apn);
+            setupHandlers(apn);
             runTest();
-        } catch (RuntimeException e) {
+        } catch (SkipTestException e) {
             // NPN availability is dependent on the java version. If NPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
@@ -79,15 +66,15 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkNpnSslEngine.isAvailable()) {
-                throw new RuntimeException("NPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.NPN);
             }
-            JdkApplicationProtocolNegotiator clientApn = new JdkNpnApplicationProtocolNegotiator(false, false,
+            ApplicationProtocolConfig clientApn = acceptingNegotiator(Protocol.NPN,
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-            JdkApplicationProtocolNegotiator serverApn = new JdkNpnApplicationProtocolNegotiator(false, false,
+            ApplicationProtocolConfig serverApn = acceptingNegotiator(Protocol.NPN,
                     APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
-            mySetup(serverApn, clientApn);
+            setupHandlers(serverApn, clientApn);
             runTest(null);
-        } catch (Exception e) {
+        } catch (SkipTestException e) {
             // ALPN availability is dependent on the java version. If ALPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
@@ -101,16 +88,16 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkNpnSslEngine.isAvailable()) {
-                throw new RuntimeException("NPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.NPN);
             }
-            JdkApplicationProtocolNegotiator clientApn = new JdkNpnApplicationProtocolNegotiator(true, true,
+            ApplicationProtocolConfig clientApn = failingNegotiator(Protocol.NPN,
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-            JdkApplicationProtocolNegotiator serverApn = new JdkNpnApplicationProtocolNegotiator(false, false,
+            ApplicationProtocolConfig serverApn = acceptingNegotiator(Protocol.NPN,
                     APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
-            mySetup(serverApn, clientApn);
+            setupHandlers(serverApn, clientApn);
             assertTrue(clientLatch.await(2, TimeUnit.SECONDS));
             assertTrue(clientException instanceof SSLHandshakeException);
-        } catch (RuntimeException e) {
+        } catch (SkipTestException e) {
             // NPN availability is dependent on the java version. If NPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
@@ -124,16 +111,16 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkNpnSslEngine.isAvailable()) {
-                throw new RuntimeException("NPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.NPN);
             }
-            JdkApplicationProtocolNegotiator clientApn = new JdkNpnApplicationProtocolNegotiator(false, false,
+            ApplicationProtocolConfig clientApn = acceptingNegotiator(Protocol.NPN,
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-            JdkApplicationProtocolNegotiator serverApn = new JdkNpnApplicationProtocolNegotiator(true, true,
+            ApplicationProtocolConfig serverApn = failingNegotiator(Protocol.NPN,
                     APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
-            mySetup(serverApn, clientApn);
+            setupHandlers(serverApn, clientApn);
             assertTrue(serverLatch.await(2, TimeUnit.SECONDS));
             assertTrue(serverException instanceof SSLHandshakeException);
-        } catch (RuntimeException e) {
+        } catch (SkipTestException e) {
             // NPN availability is dependent on the java version. If NPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
@@ -147,13 +134,13 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkAlpnSslEngine.isAvailable()) {
-                throw new RuntimeException("ALPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.ALPN);
             }
-            JdkApplicationProtocolNegotiator apn = new JdkAlpnApplicationProtocolNegotiator(true, true,
+            ApplicationProtocolConfig apn = failingNegotiator(Protocol.ALPN,
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-            mySetup(apn);
+            setupHandlers(apn);
             runTest();
-        } catch (Exception e) {
+        } catch (SkipTestException e) {
             // ALPN availability is dependent on the java version. If ALPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
@@ -167,15 +154,15 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkAlpnSslEngine.isAvailable()) {
-                throw new RuntimeException("ALPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.ALPN);
             }
-            JdkApplicationProtocolNegotiator clientApn = new JdkAlpnApplicationProtocolNegotiator(false, false,
+            ApplicationProtocolConfig clientApn = acceptingNegotiator(Protocol.ALPN,
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-            JdkApplicationProtocolNegotiator serverApn = new JdkAlpnApplicationProtocolNegotiator(false, false,
+            ApplicationProtocolConfig serverApn = acceptingNegotiator(Protocol.ALPN,
                     APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
-            mySetup(serverApn, clientApn);
+            setupHandlers(serverApn, clientApn);
             runTest(null);
-        } catch (Exception e) {
+        } catch (SkipTestException e) {
             // ALPN availability is dependent on the java version. If ALPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
@@ -189,16 +176,16 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkAlpnSslEngine.isAvailable()) {
-                throw new RuntimeException("ALPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.ALPN);
             }
-            JdkApplicationProtocolNegotiator clientApn = new JdkAlpnApplicationProtocolNegotiator(false, false,
+            ApplicationProtocolConfig clientApn = acceptingNegotiator(Protocol.ALPN,
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-            JdkApplicationProtocolNegotiator serverApn = new JdkAlpnApplicationProtocolNegotiator(true, true,
+            ApplicationProtocolConfig serverApn = failingNegotiator(Protocol.ALPN,
                     APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
-            mySetup(serverApn, clientApn);
+            setupHandlers(serverApn, clientApn);
             assertTrue(serverLatch.await(2, TimeUnit.SECONDS));
             assertTrue(serverException instanceof SSLHandshakeException);
-        } catch (Exception e) {
+        } catch (SkipTestException e) {
             // ALPN availability is dependent on the java version. If ALPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
@@ -212,18 +199,18 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkAlpnSslEngine.isAvailable()) {
-                throw new RuntimeException("ALPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.ALPN);
             }
             // Even the preferred application protocol appears second in the client's list, it will be picked
             // because it's the first one on server's list.
-            JdkApplicationProtocolNegotiator clientApn = new JdkAlpnApplicationProtocolNegotiator(false, false,
+            ApplicationProtocolConfig clientApn = acceptingNegotiator(Protocol.ALPN,
                 FALLBACK_APPLICATION_LEVEL_PROTOCOL, PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-            JdkApplicationProtocolNegotiator serverApn = new JdkAlpnApplicationProtocolNegotiator(true, true,
+            ApplicationProtocolConfig serverApn = failingNegotiator(Protocol.ALPN,
                 PREFERRED_APPLICATION_LEVEL_PROTOCOL, FALLBACK_APPLICATION_LEVEL_PROTOCOL);
-            mySetup(serverApn, clientApn);
+            setupHandlers(serverApn, clientApn);
             assertNull(serverException);
             runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL);
-        } catch (Exception e) {
+        } catch (SkipTestException e) {
             // ALPN availability is dependent on the java version. If ALPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
@@ -237,8 +224,9 @@
             // Check in this test just in case we have multiple tests that just the class and we already ignored the
             // initialization error.
             if (!JdkAlpnSslEngine.isAvailable()) {
-                throw new RuntimeException("ALPN not on classpath");
+                throw tlsExtensionNotFound(Protocol.ALPN);
             }
+            SelfSignedCertificate ssc = new SelfSignedCertificate();
             JdkApplicationProtocolNegotiator clientApn = new JdkAlpnApplicationProtocolNegotiator(true, true,
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
             JdkApplicationProtocolNegotiator serverApn = new JdkAlpnApplicationProtocolNegotiator(
@@ -258,84 +246,25 @@
                         }
                     }, JdkBaseApplicationProtocolNegotiator.FAIL_SELECTION_LISTENER_FACTORY,
                     APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
-            mySetup(serverApn, clientApn);
+
+            SslContext serverSslCtx = new JdkSslServerContext(ssc.certificate(), ssc.privateKey(), null, null,
+                    IdentityCipherSuiteFilter.INSTANCE, serverApn, 0, 0);
+            SslContext clientSslCtx = new JdkSslClientContext(null, InsecureTrustManagerFactory.INSTANCE, null,
+                    IdentityCipherSuiteFilter.INSTANCE, clientApn, 0, 0);
+
+            setupHandlers(serverSslCtx, clientSslCtx);
             assertTrue(clientLatch.await(2, TimeUnit.SECONDS));
             assertTrue(clientException instanceof SSLHandshakeException);
-        } catch (Exception e) {
+        } catch (SkipTestException e) {
             // ALPN availability is dependent on the java version. If ALPN is not available because of
             // java version incompatibility don't fail the test, but instead just skip the test
             assumeNoException(e);
         }
     }
 
-    private void mySetup(JdkApplicationProtocolNegotiator apn) throws InterruptedException, SSLException,
-            CertificateException {
-        mySetup(apn, apn);
-    }
-
-    private void mySetup(JdkApplicationProtocolNegotiator serverApn, JdkApplicationProtocolNegotiator clientApn)
-            throws InterruptedException, SSLException, CertificateException {
-        SelfSignedCertificate ssc = new SelfSignedCertificate();
-        serverSslCtx = new JdkSslServerContext(ssc.certificate(), ssc.privateKey(), null, null,
-                IdentityCipherSuiteFilter.INSTANCE, serverApn, 0, 0);
-        clientSslCtx = new JdkSslClientContext(null, InsecureTrustManagerFactory.INSTANCE, null,
-                IdentityCipherSuiteFilter.INSTANCE, clientApn, 0, 0);
-
-        serverConnectedChannel = null;
-        sb = new ServerBootstrap();
-        cb = new Bootstrap();
-
-        sb.group(new NioEventLoopGroup(), new NioEventLoopGroup());
-        sb.channel(NioServerSocketChannel.class);
-        sb.childHandler(new ChannelInitializer() {
-            @Override
-            protected void initChannel(Channel ch) throws Exception {
-                ChannelPipeline p = ch.pipeline();
-                p.addLast(serverSslCtx.newHandler(ch.alloc()));
-                p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch));
-                p.addLast(new ChannelHandlerAdapter() {
-                    @Override
-                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
-                        if (cause.getCause() instanceof SSLHandshakeException) {
-                            serverException = cause.getCause();
-                            serverLatch.countDown();
-                        } else {
-                            ctx.fireExceptionCaught(cause);
-                        }
-                    }
-                });
-                serverConnectedChannel = ch;
-            }
-        });
-
-        cb.group(new NioEventLoopGroup());
-        cb.channel(NioSocketChannel.class);
-        cb.handler(new ChannelInitializer() {
-            @Override
-            protected void initChannel(Channel ch) throws Exception {
-                ChannelPipeline p = ch.pipeline();
-                p.addLast(clientSslCtx.newHandler(ch.alloc()));
-                p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch));
-                p.addLast(new ChannelHandlerAdapter() {
-                    @Override
-                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
-                        if (cause.getCause() instanceof SSLHandshakeException) {
-                            clientException = cause.getCause();
-                            clientLatch.countDown();
-                        } else {
-                            ctx.fireExceptionCaught(cause);
-                        }
-                    }
-                });
-            }
-        });
-
-        serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel();
-        int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
-
-        ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port));
-        assertTrue(ccf.awaitUninterruptibly().isSuccess());
-        clientChannel = ccf.channel();
+    @Test
+    public void testEnablingAnAlreadyDisabledSslProtocol() throws Exception {
+        testEnablingAnAlreadyDisabledSslProtocol(new String[]{}, new String[]{PROTOCOL_TLS_V1_2});
     }
 
     private void runTest() throws Exception {
@@ -346,4 +275,30 @@
     protected SslProvider sslProvider() {
         return SslProvider.JDK;
     }
+
+    private ApplicationProtocolConfig failingNegotiator(Protocol protocol,
+                                                        String... supportedProtocols) {
+        return new ApplicationProtocolConfig(protocol,
+                SelectorFailureBehavior.FATAL_ALERT,
+                SelectedListenerFailureBehavior.FATAL_ALERT,
+                supportedProtocols);
+    }
+
+    private ApplicationProtocolConfig acceptingNegotiator(Protocol protocol,
+                                                          String... supportedProtocols) {
+        return new ApplicationProtocolConfig(protocol,
+                SelectorFailureBehavior.NO_ADVERTISE,
+                SelectedListenerFailureBehavior.ACCEPT,
+                supportedProtocols);
+    }
+
+    private SkipTestException tlsExtensionNotFound(Protocol protocol) {
+        throw new SkipTestException(protocol + " not on classpath");
+    }
+
+    private static final class SkipTestException extends RuntimeException {
+        public SkipTestException(String message) {
+            super(message);
+        }
+    }
 }
diff -Nru netty-4.0.33/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java netty-4.0.34/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java
--- netty-4.0.33/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -15,9 +15,58 @@
  */
 package io.netty.handler.ssl;
 
+import org.junit.Test;
+
+import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
+import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
+import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
+
+import static org.junit.Assert.assertNull;
 import static org.junit.Assume.assumeTrue;
 
 public class OpenSslEngineTest extends SSLEngineTest {
+    private static final String PREFERRED_APPLICATION_LEVEL_PROTOCOL = "my-protocol-http2";
+    private static final String FALLBACK_APPLICATION_LEVEL_PROTOCOL = "my-protocol-http1_1";
+
+    @Test
+    public void testNpn() throws Exception {
+        assumeTrue(OpenSsl.isAvailable());
+        ApplicationProtocolConfig apn = acceptingNegotiator(Protocol.NPN,
+                PREFERRED_APPLICATION_LEVEL_PROTOCOL);
+        setupHandlers(apn);
+        runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL);
+    }
+
+    @Test
+    public void testAlpn() throws Exception {
+        assumeTrue(OpenSsl.isAvailable());
+        assumeTrue(OpenSsl.isAlpnSupported());
+        ApplicationProtocolConfig apn = acceptingNegotiator(Protocol.ALPN,
+                PREFERRED_APPLICATION_LEVEL_PROTOCOL);
+        setupHandlers(apn);
+        runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL);
+    }
+
+    @Test
+    public void testAlpnCompatibleProtocolsDifferentClientOrder() throws Exception {
+        assumeTrue(OpenSsl.isAvailable());
+        assumeTrue(OpenSsl.isAlpnSupported());
+        ApplicationProtocolConfig clientApn = acceptingNegotiator(Protocol.ALPN,
+                FALLBACK_APPLICATION_LEVEL_PROTOCOL, PREFERRED_APPLICATION_LEVEL_PROTOCOL);
+        ApplicationProtocolConfig serverApn = acceptingNegotiator(Protocol.ALPN,
+                PREFERRED_APPLICATION_LEVEL_PROTOCOL, FALLBACK_APPLICATION_LEVEL_PROTOCOL);
+        setupHandlers(serverApn, clientApn);
+        assertNull(serverException);
+        runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL);
+    }
+
+    @Test
+    public void testEnablingAnAlreadyDisabledSslProtocol() throws Exception {
+        assumeTrue(OpenSsl.isAvailable());
+        testEnablingAnAlreadyDisabledSslProtocol(new String[]{PROTOCOL_SSL_V2_HELLO},
+            new String[]{PROTOCOL_SSL_V2_HELLO, PROTOCOL_TLS_V1_2});
+    }
+
     @Override
     public void testMutualAuthSameCerts() throws Exception {
         assumeTrue(OpenSsl.isAvailable());
@@ -58,4 +107,12 @@
     protected SslProvider sslProvider() {
         return SslProvider.OPENSSL;
     }
+
+    private static ApplicationProtocolConfig acceptingNegotiator(Protocol protocol,
+            String... supportedProtocols) {
+        return new ApplicationProtocolConfig(protocol,
+                SelectorFailureBehavior.NO_ADVERTISE,
+                SelectedListenerFailureBehavior.ACCEPT,
+                supportedProtocols);
+    }
 }
diff -Nru netty-4.0.33/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java netty-4.0.34/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java
--- netty-4.0.33/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -34,7 +34,7 @@
         File keyFile = new File(SniHandlerTest.class.getResource("test_encrypted.pem").getFile());
         File crtFile = new File(SniHandlerTest.class.getResource("test.crt").getFile());
 
-        return new JdkSslServerContext(crtFile, keyFile, "12345");
+        return SslContextBuilder.forServer(crtFile, keyFile, "12345").build();
     }
 
     @Test
diff -Nru netty-4.0.33/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java netty-4.0.34/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java
--- netty-4.0.33/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/handler/src/test/java/io/netty/handler/ssl/SSLEngineTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -22,8 +22,8 @@
 import io.netty.buffer.UnpooledByteBufAllocator;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelHandlerAdapter;
 import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelPipeline;
 import io.netty.channel.SimpleChannelInboundHandler;
@@ -49,6 +49,7 @@
 import java.io.File;
 import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
+import java.security.cert.CertificateException;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -58,10 +59,13 @@
 
 public abstract class SSLEngineTest {
 
+    protected static final String PROTOCOL_TLS_V1_2 = "TLSv1.2";
+    protected static final String PROTOCOL_SSL_V2_HELLO = "SSLv2Hello";
+
     @Mock
-    protected MessageReciever serverReceiver;
+    protected MessageReceiver serverReceiver;
     @Mock
-    protected MessageReciever clientReceiver;
+    protected MessageReceiver clientReceiver;
 
     protected Throwable serverException;
     protected Throwable clientException;
@@ -75,15 +79,15 @@
     protected CountDownLatch serverLatch;
     protected CountDownLatch clientLatch;
 
-    interface MessageReciever {
+    interface MessageReceiver {
         void messageReceived(ByteBuf msg);
     }
 
     protected static final class MessageDelegatorChannelHandler extends SimpleChannelInboundHandler {
-        private final MessageReciever receiver;
+        private final MessageReceiver receiver;
         private final CountDownLatch latch;
 
-        public MessageDelegatorChannelHandler(MessageReciever receiver, CountDownLatch latch) {
+        public MessageDelegatorChannelHandler(MessageReceiver receiver, CountDownLatch latch) {
             super(false);
             this.receiver = receiver;
             this.latch = latch;
@@ -180,13 +184,22 @@
             File servertTrustCrtFile, File serverKeyFile, File serverCrtFile, String serverKeyPassword,
             File clientTrustCrtFile, File clientKeyFile, File clientCrtFile, String clientKeyPassword)
             throws InterruptedException, SSLException {
-        serverSslCtx = SslContext.newServerContext(sslProvider(), servertTrustCrtFile, null,
-                                                   serverCrtFile, serverKeyFile, serverKeyPassword, null,
-                                                   null, IdentityCipherSuiteFilter.INSTANCE, null, 0, 0);
-        clientSslCtx = SslContext.newClientContext(sslProvider(), clientTrustCrtFile, null,
-                                                   clientCrtFile, clientKeyFile, clientKeyPassword, null,
-                                                   null, IdentityCipherSuiteFilter.INSTANCE,
-                                                   null, 0, 0);
+        serverSslCtx = SslContextBuilder.forServer(serverCrtFile, serverKeyFile, serverKeyPassword)
+                .sslProvider(sslProvider())
+                .trustManager(servertTrustCrtFile)
+                .ciphers(null, IdentityCipherSuiteFilter.INSTANCE)
+                .sessionCacheSize(0)
+                .sessionTimeout(0)
+                .build();
+
+        clientSslCtx = SslContextBuilder.forClient()
+                .sslProvider(sslProvider())
+                .trustManager(clientTrustCrtFile)
+                .keyManager(clientCrtFile, clientKeyFile, clientKeyPassword)
+                .ciphers(null, IdentityCipherSuiteFilter.INSTANCE)
+                .sessionCacheSize(0)
+                .sessionTimeout(0)
+                .build();
 
         serverConnectedChannel = null;
         sb = new ServerBootstrap();
@@ -203,7 +216,7 @@
                 engine.setNeedClientAuth(true);
                 p.addLast(new SslHandler(engine));
                 p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch));
-                p.addLast(new ChannelHandlerAdapter() {
+                p.addLast(new ChannelInboundHandlerAdapter() {
                     @Override
                     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                         if (cause.getCause() instanceof SSLHandshakeException) {
@@ -226,7 +239,7 @@
                 ChannelPipeline p = ch.pipeline();
                 p.addLast(clientSslCtx.newHandler(ch.alloc()));
                 p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch));
-                p.addLast(new ChannelHandlerAdapter() {
+                p.addLast(new ChannelInboundHandlerAdapter() {
                     @Override
                     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                         cause.printStackTrace();
@@ -273,7 +286,7 @@
     }
 
     private static void writeAndVerifyReceived(ByteBuf message, Channel sendChannel, CountDownLatch receiverLatch,
-                                               MessageReciever receiver) throws Exception {
+                                               MessageReceiver receiver) throws Exception {
         List dataCapture = null;
         try {
             sendChannel.writeAndFlush(message);
@@ -319,6 +332,40 @@
         assertFalse(session.isValid());
     }
 
+    protected void testEnablingAnAlreadyDisabledSslProtocol(String[] protocols1, String[] protocols2) throws Exception {
+        SSLEngine sslEngine = null;
+        try {
+            File serverKeyFile = new File(getClass().getResource("test_unencrypted.pem").getFile());
+            File serverCrtFile = new File(getClass().getResource("test.crt").getFile());
+            SslContext sslContext = SslContextBuilder.forServer(serverCrtFile, serverKeyFile)
+               .sslProvider(sslProvider())
+               .build();
+
+            sslEngine = sslContext.newEngine(UnpooledByteBufAllocator.DEFAULT);
+
+            // Disable all protocols
+            sslEngine.setEnabledProtocols(new String[]{});
+
+            // The only protocol that should be enabled is SSLv2Hello
+            String[] enabledProtocols = sslEngine.getEnabledProtocols();
+            assertEquals(protocols1.length, enabledProtocols.length);
+            assertArrayEquals(protocols1, enabledProtocols);
+
+            // Enable a protocol that is currently disabled
+            sslEngine.setEnabledProtocols(new String[]{PROTOCOL_TLS_V1_2});
+
+            // The protocol that was just enabled should be returned
+            enabledProtocols = sslEngine.getEnabledProtocols();
+            assertEquals(protocols2.length, enabledProtocols.length);
+            assertArrayEquals(protocols2, enabledProtocols);
+        } finally {
+            if (sslEngine != null) {
+                sslEngine.closeInbound();
+                sslEngine.closeOutbound();
+            }
+        }
+    }
+
     private static void handshake(SSLEngine clientEngine, SSLEngine serverEngine) throws SSLException {
         int netBufferSize = 17 * 1024;
         ByteBuffer cTOs = ByteBuffer.allocateDirect(netBufferSize);
@@ -350,7 +397,7 @@
             runDelegatedTasks(serverResult, serverEngine);
             cTOs.compact();
             sTOc.compact();
-        } while (isHandshaking(clientResult) && isHandshaking(serverResult));
+        } while (isHandshaking(clientResult) || isHandshaking(serverResult));
     }
 
     private static boolean isHandshaking(SSLEngineResult result) {
@@ -371,4 +418,94 @@
     }
 
     protected abstract SslProvider sslProvider();
+
+    protected void setupHandlers(ApplicationProtocolConfig apn) throws InterruptedException, SSLException,
+                                                                       CertificateException {
+        setupHandlers(apn, apn);
+    }
+
+    protected void setupHandlers(ApplicationProtocolConfig serverApn, ApplicationProtocolConfig clientApn)
+            throws InterruptedException, SSLException, CertificateException {
+        SelfSignedCertificate ssc = new SelfSignedCertificate();
+
+        setupHandlers(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey(), null)
+                        .sslProvider(sslProvider())
+                        .ciphers(null, IdentityCipherSuiteFilter.INSTANCE)
+                        .applicationProtocolConfig(serverApn)
+                        .sessionCacheSize(0)
+                        .sessionTimeout(0)
+                        .build(),
+
+                SslContextBuilder.forClient()
+                        .sslProvider(sslProvider())
+                        .applicationProtocolConfig(clientApn)
+                        .trustManager(InsecureTrustManagerFactory.INSTANCE)
+                        .ciphers(null, IdentityCipherSuiteFilter.INSTANCE)
+                        .sessionCacheSize(0)
+                        .sessionTimeout(0)
+                        .build());
+    }
+
+    protected void setupHandlers(SslContext serverCtx, SslContext clientCtx)
+            throws InterruptedException, SSLException, CertificateException {
+
+        serverSslCtx = serverCtx;
+        clientSslCtx = clientCtx;
+
+        serverConnectedChannel = null;
+        sb = new ServerBootstrap();
+        cb = new Bootstrap();
+
+        sb.group(new NioEventLoopGroup(), new NioEventLoopGroup());
+        sb.channel(NioServerSocketChannel.class);
+        sb.childHandler(new ChannelInitializer() {
+            @Override
+            protected void initChannel(Channel ch) throws Exception {
+                ChannelPipeline p = ch.pipeline();
+                p.addLast(serverSslCtx.newHandler(ch.alloc()));
+                p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch));
+                p.addLast(new ChannelInboundHandlerAdapter() {
+                    @Override
+                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+                        if (cause.getCause() instanceof SSLHandshakeException) {
+                            serverException = cause.getCause();
+                            serverLatch.countDown();
+                        } else {
+                            ctx.fireExceptionCaught(cause);
+                        }
+                    }
+                });
+                serverConnectedChannel = ch;
+            }
+        });
+
+        cb.group(new NioEventLoopGroup());
+        cb.channel(NioSocketChannel.class);
+        cb.handler(new ChannelInitializer() {
+            @Override
+            protected void initChannel(Channel ch) throws Exception {
+                ChannelPipeline p = ch.pipeline();
+                p.addLast(clientSslCtx.newHandler(ch.alloc()));
+                p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch));
+                p.addLast(new ChannelInboundHandlerAdapter() {
+                    @Override
+                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+                        if (cause.getCause() instanceof SSLHandshakeException) {
+                            clientException = cause.getCause();
+                            clientLatch.countDown();
+                        } else {
+                            ctx.fireExceptionCaught(cause);
+                        }
+                    }
+                });
+            }
+        });
+
+        serverChannel = sb.bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
+
+        ChannelFuture ccf = cb.connect(serverChannel.localAddress());
+        assertTrue(ccf.syncUninterruptibly().isSuccess());
+        clientChannel = ccf.channel();
+    }
+
 }
diff -Nru netty-4.0.33/microbench/pom.xml netty-4.0.34/microbench/pom.xml
--- netty-4.0.33/microbench/pom.xml	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/microbench/pom.xml	2016-01-29 08:23:46.000000000 +0000
@@ -20,7 +20,7 @@
   
     io.netty
     netty-parent
-    4.0.33.Final
+    4.0.34.Final
   
 
   netty-microbench
diff -Nru netty-4.0.33/pom.xml netty-4.0.34/pom.xml
--- netty-4.0.33/pom.xml	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/pom.xml	2016-01-29 08:23:46.000000000 +0000
@@ -26,7 +26,7 @@
   io.netty
   netty-parent
   pom
-  4.0.33.Final
+  4.0.34.Final
 
   Netty
   http://netty.io/
@@ -53,7 +53,7 @@
     https://github.com/netty/netty
     scm:git:git://github.com/netty/netty.git
     scm:git:ssh://git@github.com/netty/netty.git
-    netty-4.0.33.Final
+    netty-4.0.34.Final
   
 
   
@@ -122,7 +122,7 @@
       
     
     
-    
-      alpn-7latest
-      
-        [1.7,1.8)
-      
-      
-        7.1.3.v20150130
-      
-    
-    
-      npn-latest
-      
-        
-        [1.7,)
-      
-      
-        1.1.11.v20150415
-      
-    
-    
-      alpn-8latest
-      
-        [1.8,)
-      
-      
-        8.1.4.v20150727
-      
-    
-    
-      npn-7u9
-      
-        
-          java.version
-          1.7.0_9
-        
-      
-      
-        1.1.3.v20130313
-      
-    
-    
-      npn-7u10
-      
-        
-          java.version
-          1.7.0_10
-        
-      
-      
-        1.1.3.v20130313
-      
-    
-    
-      npn-7u11
-      
-        
-          java.version
-          1.7.0_11
-        
-      
-      
-        1.1.3.v20130313
-      
-    
-    
-      npn-7u13
-      
-        
-          java.version
-          1.7.0_13
-        
-      
-      
-        1.1.4.v20130313
-      
-    
-    
-      npn-7u15
-      
-        
-          java.version
-          1.7.0_15
-        
-      
-      
-        1.1.5.v20130313
-      
-    
-    
-      npn-7u17
-      
-        
-          java.version
-          1.7.0_17
-        
-      
-      
-        1.1.5.v20130313
-      
-    
-    
-      npn-7u21
-      
-        
-          java.version
-          1.7.0_21
-        
-      
-      
-        1.1.5.v20130313
-      
-    
-    
-      npn-7u25
-      
-        
-          java.version
-          1.7.0_25
-        
-      
-      
-        1.1.5.v20130313
-      
-    
-    
-      npn-alpn-7u40
-      
-        
-          java.version
-          1.7.0_40
-        
-      
-      
-        1.1.6.v20130911
-        7.1.0.v20141016
-      
-    
-    
-      npn-alpn-7u45
-      
-        
-          java.version
-          1.7.0_45
-        
-      
-      
-        1.1.6.v20130911
-        7.1.0.v20141016
-      
-    
-    
-      npn-alpn-7u51
-      
-        
-          java.version
-          1.7.0_51
-        
-      
-      
-        1.1.6.v20130911
-        7.1.0.v20141016
-      
-    
-    
-      npn-alpn-7u55
-      
-        
-          java.version
-          1.7.0_55
-        
-      
-      
-        1.1.8.v20141013
-        7.1.0.v20141016
-      
-    
-    
-      npn-alpn-7u60
-      
-        
-          java.version
-          1.7.0_60
-        
-      
-      
-        1.1.8.v20141013
-        7.1.0.v20141016
-      
-    
-    
-      npn-alpn-7u65
-      
-        
-          java.version
-          1.7.0_65
-        
-      
-      
-        1.1.8.v20141013
-        7.1.0.v20141016
-      
-    
-    
-      npn-alpn-7u67
-      
-        
-          java.version
-          1.7.0_67
-        
-      
-      
-        1.1.8.v20141013
-        7.1.0.v20141016
-      
-    
-    
-      npn-alpn-7u71
-      
-        
-          java.version
-          1.7.0_71
-        
-      
-      
-        1.1.9.v20141016
-        7.1.2.v20141202
-      
-    
-    
-      npn-alpn-7u72
-      
-        
-          java.version
-          1.7.0_72
-        
-      
-      
-        1.1.9.v20141016
-        7.1.2.v20141202
-      
-    
-    
-      npn-alpn-7u75
-      
-        
-          java.version
-          1.7.0_75
-        
-      
-      
-        1.1.10.v20150130
-        7.1.3.v20150130
-      
-    
-    
-      npn-alpn-7u76
-      
-        
-          java.version
-          1.7.0_76
-        
-      
-      
-        1.1.10.v20150130
-        7.1.3.v20150130
-      
-    
-    
-      npn-alpn-7u79
-      
-        
-          java.version
-          1.7.0_79
-        
-      
-      
-        1.1.10.v20150130
-        7.1.3.v20150130
-      
-    
-    
-      npn-alpn-7u80
-      
-        
-          java.version
-          1.7.0_80
-        
-      
-      
-        1.1.11.v20150415
-        7.1.3.v20150130
-      
-    
-    
-      alpn-8u05
-      
-        
-          java.version
-          1.8.0_05
-        
-      
-      
-        8.1.0.v20141016
-      
-    
-    
-      alpn-8u11
-      
-        
-          java.version
-          1.8.0_11
-        
-      
-      
-        8.1.0.v20141016
-      
-    
-    
-      alpn-8u20
-      
-        
-          java.version
-          1.8.0_20
-        
-      
-      
-        8.1.0.v20141016
-      
-    
-    
-      alpn-8u25
-      
-        
-          java.version
-          1.8.0_25
-        
-      
-      
-        8.1.2.v20141202
-      
-    
-    
-      alpn-8u31
-      
-        
-          java.version
-          1.8.0_31
-        
-      
-      
-        8.1.3.v20150130
-      
-    
-    
-      alpn-8u40
-      
-        
-          java.version
-          1.8.0_40
-        
-      
-      
-        8.1.3.v20150130
-      
-    
-    
-      alpn-8u45
-      
-        
-          java.version
-          1.8.0_45
-        
-      
-      
-        8.1.3.v20150130
-      
-    
-    
-      alpn-8u51
-      
-        
-          java.version
-          1.8.0_51
-        
-      
-      
-        8.1.4.v20150727
-      
-    
     
       
-    -Xbootclasspath/p:${jetty.alpn.path}
+    
+    -javaagent:${jetty.alpnAgent.path}=${jetty.alpnAgent.option}
     -D_ 
     -D_ 
     
@@ -659,21 +264,13 @@
         org.eclipse.jetty.npn
         npn-api
         1.1.1.v20141010
-      
-      
-        org.mortbay.jetty.npn
-        npn-boot
-        ${jetty.npn.version}
+        provided 
       
       
         org.eclipse.jetty.alpn
         alpn-api
         1.1.2.v20150522
-      
-      
-        org.mortbay.jetty.alpn
-        alpn-boot
-        ${jetty.alpn.version}
+        provided 
       
 
       
@@ -687,7 +284,7 @@
       
         ${project.groupId}
         netty-tcnative
-        1.1.33.Fork10
+        1.1.33.Fork11
         ${tcnative.classifier}
         compile
         true
@@ -1057,27 +654,15 @@
         maven-dependency-plugin
         
           
-            get-npn-boot
-            validate
-            
-              get
-            
-            
-              org.mortbay.jetty.npn
-              npn-boot
-              ${jetty.npn.version}
-            
-          
-          
-            get-alpn-boot
+            get-jetty-alpn-agent
             validate
             
               get
             
             
-              org.mortbay.jetty.alpn
-              alpn-boot
-              ${jetty.alpn.version}
+              kr.motd.javaagent
+              jetty-alpn-agent
+              ${jetty.alpnAgent.version}
             
           
         
@@ -1094,7 +679,7 @@
             **/TestUtil*
           
           random
-          ${argLine.common} ${argLine.bootcp} ${argLine.leak} ${argLine.coverage}
+          ${argLine.common} ${argLine.alpnAgent} ${argLine.leak} ${argLine.coverage}
         
       
       
@@ -1150,11 +735,11 @@
           
             
               2
-              ${name}
-              ${groupId}.${artifactId}.source
-              ${organization.name}
+              ${project.name}
+              ${project.groupId}.${project.artifactId}.source
+              ${project.organization.name}
               ${parsedVersion.osgiVersion}
-              ${groupId}.${artifactId};version="${parsedVersion.osgiVersion}";roots:="."
+              ${project.groupId}.${project.artifactId};version="${parsedVersion.osgiVersion}";roots:="."
             
           
         
diff -Nru netty-4.0.33/tarball/pom.xml netty-4.0.34/tarball/pom.xml
--- netty-4.0.33/tarball/pom.xml	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/tarball/pom.xml	2016-01-29 08:23:46.000000000 +0000
@@ -20,7 +20,7 @@
   
     io.netty
     netty-parent
-    4.0.33.Final
+    4.0.34.Final
   
 
   netty-tarball
diff -Nru netty-4.0.33/testsuite/pom.xml netty-4.0.34/testsuite/pom.xml
--- netty-4.0.33/testsuite/pom.xml	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/testsuite/pom.xml	2016-01-29 08:23:46.000000000 +0000
@@ -20,7 +20,7 @@
   
     io.netty
     netty-parent
-    4.0.33.Final
+    4.0.34.Final
   
 
   netty-testsuite
diff -Nru netty-4.0.33/testsuite-osgi/pom.xml netty-4.0.34/testsuite-osgi/pom.xml
--- netty-4.0.33/testsuite-osgi/pom.xml	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/testsuite-osgi/pom.xml	2016-01-29 08:23:46.000000000 +0000
@@ -20,7 +20,7 @@
   
     io.netty
     netty-parent
-    4.0.33.Final
+    4.0.34.Final
   
 
   netty-testsuite-osgi
diff -Nru netty-4.0.33/transport/pom.xml netty-4.0.34/transport/pom.xml
--- netty-4.0.33/transport/pom.xml	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/pom.xml	2016-01-29 08:23:46.000000000 +0000
@@ -20,7 +20,7 @@
   
     io.netty
     netty-parent
-    4.0.33.Final
+    4.0.34.Final
   
 
   netty-transport
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java netty-4.0.34/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java
--- netty-4.0.33/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java	2016-01-29 08:23:46.000000000 +0000
@@ -29,6 +29,7 @@
 import io.netty.util.AttributeKey;
 import io.netty.util.concurrent.EventExecutor;
 import io.netty.util.concurrent.GlobalEventExecutor;
+import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.StringUtil;
 
 import java.net.InetAddress;
@@ -341,7 +342,7 @@
 
         // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
         // the pipeline in its channelRegistered() implementation.
-        channel.eventLoop().execute(new Runnable() {
+        channel.eventLoop().execute(new OneTimeTask() {
             @Override
             public void run() {
                 if (regFuture.isSuccess()) {
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/bootstrap/Bootstrap.java netty-4.0.34/transport/src/main/java/io/netty/bootstrap/Bootstrap.java
--- netty-4.0.33/transport/src/main/java/io/netty/bootstrap/Bootstrap.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/bootstrap/Bootstrap.java	2016-01-29 08:23:46.000000000 +0000
@@ -23,6 +23,7 @@
 import io.netty.channel.ChannelPromise;
 import io.netty.channel.EventLoopGroup;
 import io.netty.util.AttributeKey;
+import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
 
@@ -158,7 +159,7 @@
 
         // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
         // the pipeline in its channelRegistered() implementation.
-        channel.eventLoop().execute(new Runnable() {
+        channel.eventLoop().execute(new OneTimeTask() {
             @Override
             public void run() {
                 if (regFuture.isSuccess()) {
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java netty-4.0.34/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java
--- netty-4.0.33/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java	2016-01-29 08:23:46.000000000 +0000
@@ -28,6 +28,7 @@
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.ServerChannel;
 import io.netty.util.AttributeKey;
+import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.StringUtil;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
@@ -274,7 +275,7 @@
                 // stop accept new connections for 1 second to allow the channel to recover
                 // See https://github.com/netty/netty/issues/1328
                 config.setAutoRead(false);
-                ctx.channel().eventLoop().schedule(new Runnable() {
+                ctx.channel().eventLoop().schedule(new OneTimeTask() {
                     @Override
                     public void run() {
                        config.setAutoRead(true);
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/AbstractChannelHandlerContext.java netty-4.0.34/transport/src/main/java/io/netty/channel/AbstractChannelHandlerContext.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/AbstractChannelHandlerContext.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/AbstractChannelHandlerContext.java	2016-01-29 08:23:46.000000000 +0000
@@ -15,27 +15,17 @@
  */
 package io.netty.channel;
 
-import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufAllocator;
-import io.netty.buffer.ByteBufHolder;
 import io.netty.util.DefaultAttributeMap;
 import io.netty.util.Recycler;
 import io.netty.util.ReferenceCountUtil;
 import io.netty.util.concurrent.EventExecutor;
-import io.netty.util.concurrent.EventExecutorGroup;
-import io.netty.util.concurrent.FastThreadLocal;
 import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.RecyclableMpscLinkedQueueNode;
 import io.netty.util.internal.StringUtil;
+import io.netty.util.internal.SystemPropertyUtil;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.net.SocketAddress;
-import java.nio.ByteBuffer;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.WeakHashMap;
 
 import static io.netty.channel.DefaultChannelPipeline.*;
 
@@ -46,10 +36,24 @@
 
     private final boolean inbound;
     private final boolean outbound;
-    private final AbstractChannel channel;
     private final DefaultChannelPipeline pipeline;
     private final String name;
-    private boolean removed;
+    private boolean handlerRemoved;
+
+    /**
+     * This is set to {@code true} once the {@link ChannelHandler#handlerAdded(ChannelHandlerContext) method is called.
+     * We need to keep track of this to ensure we will never call another {@link ChannelHandler} method before
+     * handlerAdded(...) is called to guard against ordering issues.
+     * {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} MUST be the first
+     * method that is called for handler when it becomes a part of the {@link ChannelPipeline} in all cases. Not doing
+     * so may lead to unexpected side-effects as {@link ChannelHandler} implementations may need to do initialization
+     * steps before a {@link ChannelHandler} can be used.
+     *
+     * See #4705
+     *
+     * No need to mark volatile as this will be made visible as next/prev is volatile.
+     */
+    private boolean handlerAdded;
 
     // Will be set to null if no child executor should be used, otherwise it will be set to the
     // child executor.
@@ -64,37 +68,23 @@
     private volatile Runnable invokeChannelWritableStateChangedTask;
     private volatile Runnable invokeFlushTask;
 
-    AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutorGroup group, String name,
+    AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
                                   boolean inbound, boolean outbound) {
 
         if (name == null) {
             throw new NullPointerException("name");
         }
 
-        channel = pipeline.channel;
         this.pipeline = pipeline;
         this.name = name;
-
-        if (group != null) {
-            // Pin one of the child executors once and remember it so that the same child executor
-            // is used to fire events for the same channel.
-            EventExecutor childExecutor = pipeline.childExecutors.get(group);
-            if (childExecutor == null) {
-                childExecutor = group.next();
-                pipeline.childExecutors.put(group, childExecutor);
-            }
-            executor = childExecutor;
-        } else {
-            executor = null;
-        }
-
+        this.executor = executor;
         this.inbound = inbound;
         this.outbound = outbound;
     }
 
     @Override
     public Channel channel() {
-        return channel;
+        return pipeline.channel();
     }
 
     @Override
@@ -125,7 +115,7 @@
     public ChannelHandlerContext fireChannelRegistered() {
         final AbstractChannelHandlerContext next = findContextInbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeChannelRegistered();
         } else {
             executor.execute(new OneTimeTask() {
@@ -150,7 +140,7 @@
     public ChannelHandlerContext fireChannelUnregistered() {
         final AbstractChannelHandlerContext next = findContextInbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeChannelUnregistered();
         } else {
             executor.execute(new OneTimeTask() {
@@ -175,7 +165,7 @@
     public ChannelHandlerContext fireChannelActive() {
         final AbstractChannelHandlerContext next = findContextInbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeChannelActive();
         } else {
             executor.execute(new OneTimeTask() {
@@ -200,7 +190,7 @@
     public ChannelHandlerContext fireChannelInactive() {
         final AbstractChannelHandlerContext next = findContextInbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeChannelInactive();
         } else {
             executor.execute(new OneTimeTask() {
@@ -230,7 +220,7 @@
         final AbstractChannelHandlerContext next = this.next;
 
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeExceptionCaught(cause);
         } else {
             try {
@@ -247,7 +237,6 @@
                 }
             }
         }
-
         return this;
     }
 
@@ -271,7 +260,7 @@
 
         final AbstractChannelHandlerContext next = findContextInbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeUserEventTriggered(event);
         } else {
             executor.execute(new OneTimeTask() {
@@ -300,7 +289,7 @@
 
         final AbstractChannelHandlerContext next = findContextInbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeChannelRead(msg);
         } else {
             executor.execute(new OneTimeTask() {
@@ -325,7 +314,7 @@
     public ChannelHandlerContext fireChannelReadComplete() {
         final AbstractChannelHandlerContext next = findContextInbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeChannelReadComplete();
         } else {
             Runnable task = next.invokeChannelReadCompleteTask;
@@ -354,7 +343,7 @@
     public ChannelHandlerContext fireChannelWritabilityChanged() {
         final AbstractChannelHandlerContext next = findContextInbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeChannelWritabilityChanged();
         } else {
             Runnable task = next.invokeChannelWritableStateChangedTask;
@@ -421,7 +410,7 @@
 
         final AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeBind(localAddress, promise);
         } else {
             safeExecute(executor, new OneTimeTask() {
@@ -431,7 +420,6 @@
                 }
             }, promise, null);
         }
-
         return promise;
     }
 
@@ -462,7 +450,7 @@
 
         final AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeConnect(remoteAddress, localAddress, promise);
         } else {
             safeExecute(executor, new OneTimeTask() {
@@ -472,7 +460,6 @@
                 }
             }, promise, null);
         }
-
         return promise;
     }
 
@@ -493,7 +480,7 @@
 
         final AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             // Translate disconnect to close if the channel has no notion of disconnect-reconnect.
             // So far, UDP/IP is the only transport that has such behavior.
             if (!channel().metadata().hasDisconnect()) {
@@ -513,7 +500,6 @@
                 }
             }, promise, null);
         }
-
         return promise;
     }
 
@@ -534,7 +520,7 @@
 
         final AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeClose(promise);
         } else {
             safeExecute(executor, new OneTimeTask() {
@@ -565,7 +551,7 @@
 
         final AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeDeregister(promise);
         } else {
             safeExecute(executor, new OneTimeTask() {
@@ -591,7 +577,7 @@
     public ChannelHandlerContext read() {
         final AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeRead();
         } else {
             Runnable task = next.invokeReadTask;
@@ -650,7 +636,7 @@
     public ChannelHandlerContext flush() {
         final AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeFlush();
         } else {
             Runnable task = next.invokeFlushTask;
@@ -662,7 +648,7 @@
                     }
                 };
             }
-            safeExecute(executor, task, channel.voidPromise(), null);
+            safeExecute(executor, task, channel().voidPromise(), null);
         }
 
         return this;
@@ -696,7 +682,7 @@
     private void write(Object msg, boolean flush, ChannelPromise promise) {
         AbstractChannelHandlerContext next = findContextOutbound();
         EventExecutor executor = next.executor();
-        if (executor.inEventLoop()) {
+        if (next.isHandlerAddedCalled() && executor.inEventLoop()) {
             next.invokeWrite(msg, promise);
             if (flush) {
                 next.invokeFlush();
@@ -837,16 +823,24 @@
 
     @Override
     public ChannelPromise voidPromise() {
-        return channel.voidPromise();
+        return channel().voidPromise();
     }
 
     void setRemoved() {
-        removed = true;
+        handlerRemoved = true;
     }
 
     @Override
     public boolean isRemoved() {
-        return removed;
+        return handlerRemoved;
+    }
+
+    final void setHandlerAddedCalled() {
+        handlerAdded = true;
+    }
+
+    final boolean isHandlerAddedCalled() {
+        return handlerAdded;
     }
 
     private static void safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg) {
@@ -865,23 +859,12 @@
 
     abstract static class AbstractWriteTask extends RecyclableMpscLinkedQueueNode implements Runnable {
 
-        private static final FastThreadLocal, Integer>> CLASS_SIZES =
-                new FastThreadLocal, Integer>>() {
-            @Override
-            protected Map, Integer> initialValue() throws Exception {
-                Map, Integer> map = new WeakHashMap, Integer>();
-                map.put(void.class, 0);
-                map.put(byte.class, 1);
-                map.put(char.class, 2);
-                map.put(short.class, 2);
-                map.put(boolean.class, 4); // Probably an integer.
-                map.put(int.class, 4);
-                map.put(float.class, 4);
-                map.put(long.class, 8);
-                map.put(double.class, 8);
-                return map;
-            }
-        };
+        private static final boolean ESTIMATE_TASK_SIZE_ON_SUBMIT =
+                SystemPropertyUtil.getBoolean("io.netty.transport.estimateSizeOnSubmit", true);
+
+        // Assuming a 64-bit JVM, 16 bytes object header, 3 reference fields and one int field, plus alignment
+        private static final int WRITE_TASK_OVERHEAD =
+                SystemPropertyUtil.getInt("io.netty.transport.writeTaskSizeOverhead", 48);
 
         private AbstractChannelHandlerContext ctx;
         private Object msg;
@@ -892,97 +875,34 @@
             super(handle);
         }
 
-        private static int estimateSize(Object o, Map, Integer> classSizes) {
-            int answer = 8 + estimateSize(o.getClass(), classSizes, null);
-
-            if (o instanceof ByteBuf) {
-                answer += ((ByteBuf) o).readableBytes();
-            } else if (o instanceof ByteBufHolder) {
-                answer += ((ByteBufHolder) o).content().readableBytes();
-            } else if (o instanceof FileRegion) {
-                // nothing to add.
-            } else if (o instanceof byte[]) {
-                answer += ((byte[]) o).length;
-            } else if (o instanceof ByteBuffer) {
-                answer += ((ByteBuffer) o).remaining();
-            } else if (o instanceof CharSequence) {
-                answer += ((CharSequence) o).length() << 1;
-            } else if (o instanceof Iterable) {
-                for (Object m : (Iterable) o) {
-                    answer += estimateSize(m, classSizes);
-                }
-            }
-
-            return align(answer);
-        }
-
-        private static int estimateSize(Class clazz, Map, Integer> classSizes,
-                                        Set> visitedClasses) {
-            Integer objectSize = classSizes.get(clazz);
-            if (objectSize != null) {
-                return objectSize;
-            }
-
-            if (visitedClasses != null) {
-                if (visitedClasses.contains(clazz)) {
-                    return 0;
-                }
-            } else {
-                visitedClasses = new HashSet>();
-            }
-
-            visitedClasses.add(clazz);
-
-            int answer = 8; // Basic overhead.
-            for (Class c = clazz; c != null; c = c.getSuperclass()) {
-                Field[] fields = c.getDeclaredFields();
-                for (Field f : fields) {
-                    if ((f.getModifiers() & Modifier.STATIC) != 0) {
-                        // Ignore static fields.
-                        continue;
-                    }
-
-                    answer += estimateSize(f.getType(), classSizes, visitedClasses);
-                }
-            }
-
-            visitedClasses.remove(clazz);
-
-            // Some alignment.
-            answer = align(answer);
-
-            // Put the final answer.
-            classSizes.put(clazz, answer);
-            return answer;
-        }
-
-        private static int align(int size) {
-            return size + 8 - (size & 7);
-        }
-
         protected static void init(AbstractWriteTask task, AbstractChannelHandlerContext ctx,
                                    Object msg, ChannelPromise promise) {
             task.ctx = ctx;
             task.msg = msg;
             task.promise = promise;
-            task.size = ctx.channel.estimatorHandle().size(msg) + estimateSize(task, CLASS_SIZES.get());
 
-            ChannelOutboundBuffer buffer = ctx.channel.unsafe().outboundBuffer();
-            // Check for null as it may be set to null if the channel is closed already
-            if (buffer != null) {
-                buffer.incrementPendingOutboundBytes(task.size);
+            if (ESTIMATE_TASK_SIZE_ON_SUBMIT) {
+                ChannelOutboundBuffer buffer = ctx.channel().unsafe().outboundBuffer();
+
+                // Check for null as it may be set to null if the channel is closed already
+                if (buffer != null) {
+                    task.size = ((AbstractChannel) ctx.channel()).estimatorHandle().size(msg) + WRITE_TASK_OVERHEAD;
+                    buffer.incrementPendingOutboundBytes(task.size);
+                } else {
+                    task.size = 0;
+                }
+            } else {
+                task.size = 0;
             }
         }
 
         @Override
         public final void run() {
             try {
-                if (size > 0) {
-                    ChannelOutboundBuffer buffer = ctx.channel.unsafe().outboundBuffer();
-                    // Check for null as it may be set to null if the channel is closed already
-                    if (buffer != null) {
-                        buffer.decrementPendingOutboundBytes(size);
-                    }
+                ChannelOutboundBuffer buffer = ctx.channel().unsafe().outboundBuffer();
+                // Check for null as it may be set to null if the channel is closed already
+                if (ESTIMATE_TASK_SIZE_ON_SUBMIT && buffer != null) {
+                    buffer.decrementPendingOutboundBytes(size);
                 }
                 write(ctx, msg, promise);
             } finally {
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/AbstractChannel.java netty-4.0.34/transport/src/main/java/io/netty/channel/AbstractChannel.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/AbstractChannel.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/AbstractChannel.java	2016-01-29 08:23:46.000000000 +0000
@@ -562,7 +562,7 @@
 
             final boolean wasActive = isActive();
             this.outboundBuffer = null; // Disallow adding any messages and flushes to outboundBuffer.
-            Executor closeExecutor = closeExecutor();
+            Executor closeExecutor = prepareToClose();
             if (closeExecutor != null) {
                 closeExecutor.execute(new OneTimeTask() {
                     @Override
@@ -618,16 +618,7 @@
         }
 
         private void fireChannelInactiveAndDeregister(final boolean wasActive) {
-            if (wasActive && !isActive()) {
-                invokeLater(new OneTimeTask() {
-                    @Override
-                    public void run() {
-                        pipeline.fireChannelInactive();
-                    }
-                });
-            }
-
-            deregister(voidPromise());
+            deregister(voidPromise(), wasActive && !isActive());
         }
 
         @Override
@@ -641,6 +632,10 @@
 
         @Override
         public final void deregister(final ChannelPromise promise) {
+           deregister(promise, false);
+        }
+
+        private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
             if (!promise.setUncancellable()) {
                 return;
             }
@@ -650,27 +645,38 @@
                 return;
             }
 
-            try {
-                doDeregister();
-            } catch (Throwable t) {
-                logger.warn("Unexpected exception occurred while deregistering a channel.", t);
-            } finally {
-                if (registered) {
-                    registered = false;
-                    invokeLater(new OneTimeTask() {
-                        @Override
-                        public void run() {
+            // As a user may call deregister() from within any method while doing processing in the ChannelPipeline,
+            // we need to ensure we do the actual deregister operation later. This is needed as for example,
+            // we may be in the ByteToMessageDecoder.callDecode(...) method and so still try to do processing in
+            // the old EventLoop while the user already registered the Channel to a new EventLoop. Without delay,
+            // the deregister operation this could lead to have a handler invoked by different EventLoop and so
+            // threads.
+            //
+            // See:
+            // https://github.com/netty/netty/issues/4435
+            invokeLater(new OneTimeTask() {
+                @Override
+                public void run() {
+                    try {
+                        doDeregister();
+                    } catch (Throwable t) {
+                        logger.warn("Unexpected exception occurred while deregistering a channel.", t);
+                    } finally {
+                        if (fireChannelInactive) {
+                            pipeline.fireChannelInactive();
+                        }
+                        // Some transports like local and AIO does not allow the deregistration of
+                        // an open channel.  Their doDeregister() calls close(). Consequently,
+                        // close() calls deregister() again - no need to fire channelUnregistered, so check
+                        // if it was registered.
+                        if (registered) {
+                            registered = false;
                             pipeline.fireChannelUnregistered();
                         }
-                    });
-                    safeSetSuccess(promise);
-                } else {
-                    // Some transports like local and AIO does not allow the deregistration of
-                    // an open channel.  Their doDeregister() calls close().  Consequently,
-                    // close() calls deregister() again - no need to fire channelUnregistered.
-                    safeSetSuccess(promise);
+                        safeSetSuccess(promise);
+                    }
                 }
-            }
+            });
         }
 
         @Override
@@ -862,11 +868,12 @@
         }
 
         /**
-         * @return {@link Executor} to execute {@link #doClose()} or {@code null} if it should be done in the
-         * {@link EventLoop}.
-         +
+         * Prepares to close the {@link Channel}. If this method returns an {@link Executor}, the
+         * caller must call the {@link Executor#execute(Runnable)} method with a task that calls
+         * {@link #doClose()} on the returned {@link Executor}. If this method returns {@code null},
+         * {@link #doClose()} must be called from the caller thread. (i.e. {@link EventLoop})
          */
-        protected Executor closeExecutor() {
+        protected Executor prepareToClose() {
             return null;
         }
     }
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelHandlerContext.java netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelHandlerContext.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelHandlerContext.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelHandlerContext.java	2016-01-29 08:23:46.000000000 +0000
@@ -18,7 +18,6 @@
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufAllocator;
-import io.netty.util.Attribute;
 import io.netty.util.AttributeKey;
 import io.netty.util.AttributeMap;
 import io.netty.util.concurrent.EventExecutor;
@@ -93,9 +92,8 @@
  *   // This handler will receive a sequence of increasing integers starting
  *   // from 1.
  *   {@code @Override}
- *   public void channelRead({@link ChannelHandlerContext} ctx, {@link Integer} integer) {
- *     {@link Attribute}<{@link Integer}> attr = ctx.getAttr(counter);
- *     Integer a = ctx.getAttr(counter).get();
+ *   public void channelRead({@link ChannelHandlerContext} ctx, Object msg) {
+ *     Integer a = ctx.attr(counter).get();
  *
  *     if (a == null) {
  *       a = 1;
@@ -155,7 +153,7 @@
     ChannelHandler handler();
 
     /**
-     * Return {@code true} if the {@link ChannelHandler} which belongs to this {@link ChannelHandler} was removed
+     * Return {@code true} if the {@link ChannelHandler} which belongs to this context was removed
      * from the {@link ChannelPipeline}. Note that this method is only meant to be called from with in the
      * {@link EventLoop}.
      */
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelHandler.java netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelHandler.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelHandler.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelHandler.java	2016-01-29 08:23:46.000000000 +0000
@@ -189,7 +189,10 @@
 
     /**
      * Gets called if a {@link Throwable} was thrown.
+     *
+     * @deprecated is part of {@link ChannelInboundHandler}
      */
+    @Deprecated
     void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
 
     /**
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelInboundHandler.java netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelInboundHandler.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelInboundHandler.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelInboundHandler.java	2016-01-29 08:23:46.000000000 +0000
@@ -70,5 +70,6 @@
      * Gets called if a {@link Throwable} was thrown.
      */
     @Override
+    @SuppressWarnings("deprecated")
     void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
 }
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelMetadata.java netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelMetadata.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelMetadata.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelMetadata.java	2016-01-29 08:23:46.000000000 +0000
@@ -29,7 +29,7 @@
      *
      * @param hasDisconnect     {@code true} if and only if the channel has the {@code disconnect()} operation
      *                          that allows a user to disconnect and then call {@link Channel#connect(SocketAddress)}
-     *                                      again, such as UDP/IP.
+     *                          again, such as UDP/IP.
      */
     public ChannelMetadata(boolean hasDisconnect) {
         this.hasDisconnect = hasDisconnect;
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelOption.java netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelOption.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelOption.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelOption.java	2016-01-29 08:23:46.000000000 +0000
@@ -55,8 +55,8 @@
     /**
      * @deprecated From version 5.0, {@link Channel} will not be closed on write failure.
      *
-     * {@code true} if and only if the {@link Channel} is closed automatically and immediately on write failure.
-     * The default is {@code false}.
+     * If {@code true} then the {@link Channel} is closed automatically and immediately on write failure.
+     * The default value is {@code true}.
      */
     @Deprecated
     public static final ChannelOption AUTO_CLOSE = valueOf("AUTO_CLOSE");
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelOutboundBuffer.java netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelOutboundBuffer.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelOutboundBuffer.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelOutboundBuffer.java	2016-01-29 08:23:46.000000000 +0000
@@ -24,6 +24,7 @@
 import io.netty.util.ReferenceCountUtil;
 import io.netty.util.concurrent.FastThreadLocal;
 import io.netty.util.internal.InternalThreadLocalMap;
+import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.PlatformDependent;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
@@ -629,7 +630,7 @@
 
     void close(final ClosedChannelException cause) {
         if (inFail) {
-            channel.eventLoop().execute(new Runnable() {
+            channel.eventLoop().execute(new OneTimeTask() {
                 @Override
                 public void run() {
                     close(cause);
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelOutboundHandlerAdapter.java netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelOutboundHandlerAdapter.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelOutboundHandlerAdapter.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelOutboundHandlerAdapter.java	2016-01-29 08:23:46.000000000 +0000
@@ -18,7 +18,7 @@
 import java.net.SocketAddress;
 
 /**
- * Skelton implementation of a {@link ChannelOutboundHandler}. This implementation just forwards each method call via
+ * Skeleton implementation of a {@link ChannelOutboundHandler}. This implementation just forwards each method call via
  * the {@link ChannelHandlerContext}.
  */
 public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
@@ -72,7 +72,7 @@
     }
 
     /**
-     * Calls {@link ChannelHandlerContext#close(ChannelPromise)} to forward
+     * Calls {@link ChannelHandlerContext#deregister(ChannelPromise)} to forward
      * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
      *
      * Sub-classes may override this method to change behavior.
@@ -94,7 +94,7 @@
     }
 
     /**
-     * Calls {@link ChannelHandlerContext#write(Object)} to forward
+     * Calls {@link ChannelHandlerContext#write(Object, ChannelPromise)} to forward
      * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
      *
      * Sub-classes may override this method to change behavior.
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelPipeline.java netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelPipeline.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/ChannelPipeline.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/ChannelPipeline.java	2016-01-29 08:23:46.000000000 +0000
@@ -660,7 +660,7 @@
     ChannelPipeline fireChannelRead(Object msg);
 
     /**
-     * Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)}
+     * Triggers an {@link ChannelInboundHandler#channelReadComplete(ChannelHandlerContext)}
      * event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
      */
     ChannelPipeline fireChannelReadComplete();
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/CombinedChannelDuplexHandler.java netty-4.0.34/transport/src/main/java/io/netty/channel/CombinedChannelDuplexHandler.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/CombinedChannelDuplexHandler.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/CombinedChannelDuplexHandler.java	2016-01-29 08:23:46.000000000 +0000
@@ -15,15 +15,28 @@
  */
 package io.netty.channel;
 
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import io.netty.util.concurrent.EventExecutor;
+import io.netty.util.internal.OneTimeTask;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+
 import java.net.SocketAddress;
 
 /**
  *  Combines a {@link ChannelInboundHandler} and a {@link ChannelOutboundHandler} into one {@link ChannelHandler}.
- *
  */
 public class CombinedChannelDuplexHandler
         extends ChannelDuplexHandler {
 
+    private static final InternalLogger logger = InternalLoggerFactory.getInstance(CombinedChannelDuplexHandler.class);
+
+    private DelegatingChannelHandlerContext inboundCtx;
+    private DelegatingChannelHandlerContext outboundCtx;
+    private volatile boolean handlerAdded;
+
     private I inboundHandler;
     private O outboundHandler;
 
@@ -88,6 +101,28 @@
         return outboundHandler;
     }
 
+    private void checkAdded() {
+        if (!handlerAdded) {
+            throw new IllegalStateException("handler not added to pipeline yet");
+        }
+    }
+
+    /**
+     * Removes the {@link ChannelInboundHandler} that was combined in this {@link CombinedChannelDuplexHandler}.
+     */
+    public final void removeInboundHandler() {
+        checkAdded();
+        inboundCtx.remove();
+    }
+
+    /**
+     * Removes the {@link ChannelOutboundHandler} that was combined in this {@link CombinedChannelDuplexHandler}.
+     */
+    public final void removeOutboundHandler() {
+        checkAdded();
+        outboundCtx.remove();
+    }
+
     @Override
     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
         if (inboundHandler == null) {
@@ -96,67 +131,151 @@
                             " if " +  CombinedChannelDuplexHandler.class.getSimpleName() +
                             " was constructed with the default constructor.");
         }
+
+        outboundCtx = new DelegatingChannelHandlerContext(ctx, outboundHandler);
+        inboundCtx = new DelegatingChannelHandlerContext(ctx, inboundHandler) {
+            @SuppressWarnings("deprecation")
+            @Override
+            public ChannelHandlerContext fireExceptionCaught(Throwable cause) {
+                if (!outboundCtx.removed) {
+                    try {
+                        // We directly delegate to the ChannelOutboundHandler as this may override exceptionCaught(...)
+                        // as well
+                        outboundHandler.exceptionCaught(outboundCtx, cause);
+                    } catch (Throwable error) {
+                        if (logger.isWarnEnabled()) {
+                            logger.warn(
+                                    "An exception was thrown by a user handler's " +
+                                            "exceptionCaught() method while handling the following exception:", error);
+                        }
+                    }
+                } else {
+                    super.fireExceptionCaught(cause);
+                }
+                return this;
+            }
+        };
+
+        // The inboundCtx and outboundCtx were created and set now it's safe to call removeInboundHandler() and
+        // removeOutboundHandler().
+        handlerAdded = true;
+
         try {
-            inboundHandler.handlerAdded(ctx);
+            inboundHandler.handlerAdded(inboundCtx);
         } finally {
-            outboundHandler.handlerAdded(ctx);
+            outboundHandler.handlerAdded(outboundCtx);
         }
     }
 
     @Override
     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
         try {
-            inboundHandler.handlerRemoved(ctx);
+            inboundCtx.remove();
         } finally {
-            outboundHandler.handlerRemoved(ctx);
+            outboundCtx.remove();
         }
     }
 
     @Override
     public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
-        inboundHandler.channelRegistered(ctx);
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.channelRegistered(inboundCtx);
+        } else {
+            inboundCtx.fireChannelRegistered();
+        }
     }
 
     @Override
     public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
-        inboundHandler.channelUnregistered(ctx);
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.channelUnregistered(inboundCtx);
+        } else {
+            inboundCtx.fireChannelUnregistered();
+        }
     }
 
     @Override
     public void channelActive(ChannelHandlerContext ctx) throws Exception {
-        inboundHandler.channelActive(ctx);
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.channelActive(inboundCtx);
+        } else {
+            inboundCtx.fireChannelActive();
+        }
     }
 
     @Override
     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
-        inboundHandler.channelInactive(ctx);
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.channelInactive(inboundCtx);
+        } else {
+            inboundCtx.fireChannelInactive();
+        }
     }
 
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
-        inboundHandler.exceptionCaught(ctx, cause);
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.exceptionCaught(inboundCtx, cause);
+        } else {
+            inboundCtx.fireExceptionCaught(cause);
+        }
     }
 
     @Override
     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
-        inboundHandler.userEventTriggered(ctx, evt);
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.userEventTriggered(inboundCtx, evt);
+        } else {
+            inboundCtx.fireUserEventTriggered(evt);
+        }
     }
 
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
-        inboundHandler.channelRead(ctx, msg);
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.channelRead(inboundCtx, msg);
+        } else {
+            inboundCtx.fireChannelRead(msg);
+        }
     }
 
     @Override
     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
-        inboundHandler.channelReadComplete(ctx);
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.channelReadComplete(inboundCtx);
+        } else {
+            inboundCtx.fireChannelReadComplete();
+        }
+    }
+
+    @Override
+    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
+        assert ctx == inboundCtx.ctx;
+        if (!inboundCtx.removed) {
+            inboundHandler.channelWritabilityChanged(inboundCtx);
+        } else {
+            inboundCtx.fireChannelWritabilityChanged();
+        }
     }
 
     @Override
     public void bind(
             ChannelHandlerContext ctx,
             SocketAddress localAddress, ChannelPromise promise) throws Exception {
-        outboundHandler.bind(ctx, localAddress, promise);
+        assert ctx == outboundCtx.ctx;
+        if (!outboundCtx.removed) {
+            outboundHandler.bind(outboundCtx, localAddress, promise);
+        } else {
+            outboundCtx.bind(localAddress, promise);
+        }
     }
 
     @Override
@@ -164,41 +283,321 @@
             ChannelHandlerContext ctx,
             SocketAddress remoteAddress, SocketAddress localAddress,
             ChannelPromise promise) throws Exception {
-        outboundHandler.connect(ctx, remoteAddress, localAddress, promise);
+        assert ctx == outboundCtx.ctx;
+        if (!outboundCtx.removed) {
+            outboundHandler.connect(outboundCtx, remoteAddress, localAddress, promise);
+        } else {
+            outboundCtx.connect(localAddress, promise);
+        }
     }
 
     @Override
     public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
-        outboundHandler.disconnect(ctx, promise);
+        assert ctx == outboundCtx.ctx;
+        if (!outboundCtx.removed) {
+            outboundHandler.disconnect(outboundCtx, promise);
+        } else {
+            outboundCtx.disconnect(promise);
+        }
     }
 
     @Override
     public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
-        outboundHandler.close(ctx, promise);
+        assert ctx == outboundCtx.ctx;
+        if (!outboundCtx.removed) {
+            outboundHandler.close(outboundCtx, promise);
+        } else {
+            outboundCtx.close(promise);
+        }
     }
 
     @Override
     public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
-        outboundHandler.deregister(ctx, promise);
+        assert ctx == outboundCtx.ctx;
+        if (!outboundCtx.removed) {
+            outboundHandler.deregister(outboundCtx, promise);
+        } else {
+            outboundCtx.deregister(promise);
+        }
     }
 
     @Override
     public void read(ChannelHandlerContext ctx) throws Exception {
-        outboundHandler.read(ctx);
+        assert ctx == outboundCtx.ctx;
+        if (!outboundCtx.removed) {
+            outboundHandler.read(outboundCtx);
+        } else {
+            outboundCtx.read();
+        }
     }
 
     @Override
     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
-        outboundHandler.write(ctx, msg, promise);
+        assert ctx == outboundCtx.ctx;
+        if (!outboundCtx.removed) {
+            outboundHandler.write(outboundCtx, msg, promise);
+        } else {
+            outboundCtx.write(msg, promise);
+        }
     }
 
     @Override
     public void flush(ChannelHandlerContext ctx) throws Exception {
-        outboundHandler.flush(ctx);
+        assert ctx == outboundCtx.ctx;
+        if (!outboundCtx.removed) {
+            outboundHandler.flush(outboundCtx);
+        } else {
+            outboundCtx.flush();
+        }
     }
 
-    @Override
-    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
-        inboundHandler.channelWritabilityChanged(ctx);
+    private static class DelegatingChannelHandlerContext implements ChannelHandlerContext {
+
+        private final ChannelHandlerContext ctx;
+        private final ChannelHandler handler;
+        boolean removed;
+
+        DelegatingChannelHandlerContext(ChannelHandlerContext ctx, ChannelHandler handler) {
+            this.ctx = ctx;
+            this.handler = handler;
+        }
+
+        @Override
+        public Channel channel() {
+            return ctx.channel();
+        }
+
+        @Override
+        public EventExecutor executor() {
+            return ctx.executor();
+        }
+
+        @Override
+        public String name() {
+            return ctx.name();
+        }
+
+        @Override
+        public ChannelHandler handler() {
+            return ctx.handler();
+        }
+
+        @Override
+        public boolean isRemoved() {
+            return removed || ctx.isRemoved();
+        }
+
+        @Override
+        public ChannelHandlerContext fireChannelRegistered() {
+            ctx.fireChannelRegistered();
+            return this;
+        }
+
+        @Override
+        public ChannelHandlerContext fireChannelUnregistered() {
+            ctx.fireChannelUnregistered();
+            return this;
+        }
+
+        @Override
+        public ChannelHandlerContext fireChannelActive() {
+            ctx.fireChannelActive();
+            return this;
+        }
+
+        @Override
+        public ChannelHandlerContext fireChannelInactive() {
+            ctx.fireChannelInactive();
+            return this;
+        }
+
+        @Override
+        public ChannelHandlerContext fireExceptionCaught(Throwable cause) {
+            ctx.fireExceptionCaught(cause);
+            return this;
+        }
+
+        @Override
+        public ChannelHandlerContext fireUserEventTriggered(Object event) {
+            ctx.fireUserEventTriggered(event);
+            return this;
+        }
+
+        @Override
+        public ChannelHandlerContext fireChannelRead(Object msg) {
+            ctx.fireChannelRead(msg);
+            return this;
+        }
+
+        @Override
+        public ChannelHandlerContext fireChannelReadComplete() {
+            ctx.fireChannelReadComplete();
+            return this;
+        }
+
+        @Override
+        public ChannelHandlerContext fireChannelWritabilityChanged() {
+            ctx.fireChannelWritabilityChanged();
+            return this;
+        }
+
+        @Override
+        public ChannelFuture bind(SocketAddress localAddress) {
+            return ctx.bind(localAddress);
+        }
+
+        @Override
+        public ChannelFuture connect(SocketAddress remoteAddress) {
+            return ctx.connect(remoteAddress);
+        }
+
+        @Override
+        public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
+            return ctx.connect(remoteAddress, localAddress);
+        }
+
+        @Override
+        public ChannelFuture disconnect() {
+            return ctx.disconnect();
+        }
+
+        @Override
+        public ChannelFuture close() {
+            return ctx.close();
+        }
+
+        @Override
+        public ChannelFuture deregister() {
+            return ctx.deregister();
+        }
+
+        @Override
+        public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
+            return ctx.bind(localAddress, promise);
+        }
+
+        @Override
+        public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
+            return ctx.connect(remoteAddress, promise);
+        }
+
+        @Override
+        public ChannelFuture connect(
+                SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
+            return ctx.connect(remoteAddress, localAddress, promise);
+        }
+
+        @Override
+        public ChannelFuture disconnect(ChannelPromise promise) {
+            return ctx.disconnect(promise);
+        }
+
+        @Override
+        public ChannelFuture close(ChannelPromise promise) {
+            return ctx.close(promise);
+        }
+
+        @Override
+        public ChannelFuture deregister(ChannelPromise promise) {
+            return ctx.deregister();
+        }
+
+        @Override
+        public ChannelHandlerContext read() {
+            ctx.read();
+            return this;
+        }
+
+        @Override
+        public ChannelFuture write(Object msg) {
+            return ctx.write(msg);
+        }
+
+        @Override
+        public ChannelFuture write(Object msg, ChannelPromise promise) {
+            return ctx.write(msg, promise);
+        }
+
+        @Override
+        public ChannelHandlerContext flush() {
+            ctx.flush();
+            return this;
+        }
+
+        @Override
+        public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
+            return ctx.writeAndFlush(msg, promise);
+        }
+
+        @Override
+        public ChannelFuture writeAndFlush(Object msg) {
+            return ctx.writeAndFlush(msg);
+        }
+
+        @Override
+        public ChannelPipeline pipeline() {
+            return ctx.pipeline();
+        }
+
+        @Override
+        public ByteBufAllocator alloc() {
+            return ctx.alloc();
+        }
+
+        @Override
+        public ChannelPromise newPromise() {
+            return ctx.newPromise();
+        }
+
+        @Override
+        public ChannelProgressivePromise newProgressivePromise() {
+            return ctx.newProgressivePromise();
+        }
+
+        @Override
+        public ChannelFuture newSucceededFuture() {
+            return ctx.newSucceededFuture();
+        }
+
+        @Override
+        public ChannelFuture newFailedFuture(Throwable cause) {
+            return ctx.newFailedFuture(cause);
+        }
+
+        @Override
+        public ChannelPromise voidPromise() {
+            return ctx.voidPromise();
+        }
+
+        @Override
+        public  Attribute attr(AttributeKey key) {
+            return ctx.attr(key);
+        }
+
+        final void remove() {
+            EventExecutor executor = executor();
+            if (executor.inEventLoop()) {
+                remove0();
+            } else {
+                executor.execute(new OneTimeTask() {
+                    @Override
+                    public void run() {
+                        remove0();
+                    }
+                });
+            }
+        }
+
+        private void remove0() {
+            if (!removed) {
+                removed = true;
+                try {
+                    handler.handlerRemoved(this);
+                } catch (Throwable cause) {
+                    fireExceptionCaught(new ChannelPipelineException(
+                            handler.getClass().getName() + ".handlerRemoved() has thrown an exception.", cause));
+                }
+            }
+        }
     }
 }
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/DefaultChannelHandlerContext.java netty-4.0.34/transport/src/main/java/io/netty/channel/DefaultChannelHandlerContext.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/DefaultChannelHandlerContext.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/DefaultChannelHandlerContext.java	2016-01-29 08:23:46.000000000 +0000
@@ -15,15 +15,15 @@
 */
 package io.netty.channel;
 
-import io.netty.util.concurrent.EventExecutorGroup;
+import io.netty.util.concurrent.EventExecutor;
 
 final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
 
     private final ChannelHandler handler;
 
     DefaultChannelHandlerContext(
-            DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, ChannelHandler handler) {
-        super(pipeline, group, name, isInbound(handler), isOutbound(handler));
+            DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
+        super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
         if (handler == null) {
             throw new NullPointerException("handler");
         }
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java netty-4.0.34/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java	2016-01-29 08:23:46.000000000 +0000
@@ -19,6 +19,7 @@
 import io.netty.util.ReferenceCountUtil;
 import io.netty.util.concurrent.EventExecutor;
 import io.netty.util.concurrent.EventExecutorGroup;
+import io.netty.util.concurrent.FastThreadLocal;
 import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.PlatformDependent;
 import io.netty.util.internal.StringUtil;
@@ -27,7 +28,6 @@
 
 import java.net.SocketAddress;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -46,26 +46,20 @@
 
     static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class);
 
-    @SuppressWarnings("unchecked")
-    private static final WeakHashMap, String>[] nameCaches =
-            new WeakHashMap[Runtime.getRuntime().availableProcessors()];
-
-    static {
-        for (int i = 0; i < nameCaches.length; i ++) {
-            nameCaches[i] = new WeakHashMap, String>();
+    private static final FastThreadLocal, String>> nameCaches =
+            new FastThreadLocal, String>>() {
+        @Override
+        protected Map, String> initialValue() throws Exception {
+            return new WeakHashMap, String>();
         }
-    }
+    };
 
     final AbstractChannel channel;
 
     final AbstractChannelHandlerContext head;
     final AbstractChannelHandlerContext tail;
 
-    private final Map name2ctx =
-        new HashMap(4);
-
-    final Map childExecutors =
-            new IdentityHashMap();
+    private Map childExecutors;
 
     public DefaultChannelPipeline(AbstractChannel channel) {
         if (channel == null) {
@@ -74,12 +68,37 @@
         this.channel = channel;
 
         tail = new TailContext(this);
+        tail.setHandlerAddedCalled();
         head = new HeadContext(this);
+        head.setHandlerAddedCalled();
 
         head.next = tail;
         tail.prev = head;
     }
 
+    private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
+        return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
+    }
+
+    private EventExecutor childExecutor(EventExecutorGroup group) {
+        if (group == null) {
+            return null;
+        }
+        Map childExecutors = this.childExecutors;
+        if (childExecutors == null) {
+            // Use size of 4 as most people only use one extra EventExecutor.
+            childExecutors = this.childExecutors = new IdentityHashMap(4);
+        }
+        // Pin one of the child executors once and remember it so that the same child executor
+        // is used to fire events for the same channel.
+        EventExecutor childExecutor = childExecutors.get(group);
+        if (childExecutor == null) {
+            childExecutor = group.next();
+            childExecutors.put(group, childExecutor);
+        }
+        return childExecutor;
+    }
+
     @Override
     public Channel channel() {
         return channel;
@@ -91,17 +110,14 @@
     }
 
     @Override
-    public ChannelPipeline addFirst(EventExecutorGroup group, final String name, ChannelHandler handler) {
-        synchronized (this) {
-            checkDuplicateName(name);
-            AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
-            addFirst0(name, newCtx);
-        }
-
+    public synchronized ChannelPipeline addFirst(EventExecutorGroup group, final String name, ChannelHandler handler) {
+        checkDuplicateName(name);
+        AbstractChannelHandlerContext newCtx = newContext(group, name, handler);
+        addFirst0(newCtx);
         return this;
     }
 
-    private void addFirst0(String name, AbstractChannelHandlerContext newCtx) {
+    private void addFirst0(AbstractChannelHandlerContext newCtx) {
         checkMultiplicity(newCtx);
 
         AbstractChannelHandlerContext nextCtx = head.next;
@@ -110,8 +126,6 @@
         head.next = newCtx;
         nextCtx.prev = newCtx;
 
-        name2ctx.put(name, newCtx);
-
         callHandlerAdded(newCtx);
     }
 
@@ -121,18 +135,14 @@
     }
 
     @Override
-    public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) {
-        synchronized (this) {
-            checkDuplicateName(name);
-
-            AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
-            addLast0(name, newCtx);
-        }
-
+    public synchronized ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) {
+        checkDuplicateName(name);
+        AbstractChannelHandlerContext newCtx = newContext(group, name, handler);
+        addLast0(newCtx);
         return this;
     }
 
-    private void addLast0(final String name, AbstractChannelHandlerContext newCtx) {
+    private void addLast0(AbstractChannelHandlerContext newCtx) {
         checkMultiplicity(newCtx);
 
         AbstractChannelHandlerContext prev = tail.prev;
@@ -141,8 +151,6 @@
         prev.next = newCtx;
         tail.prev = newCtx;
 
-        name2ctx.put(name, newCtx);
-
         callHandlerAdded(newCtx);
     }
 
@@ -152,19 +160,16 @@
     }
 
     @Override
-    public ChannelPipeline addBefore(
+    public synchronized ChannelPipeline addBefore(
             EventExecutorGroup group, String baseName, final String name, ChannelHandler handler) {
-        synchronized (this) {
-            AbstractChannelHandlerContext ctx = getContextOrDie(baseName);
-            checkDuplicateName(name);
-            AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
-            addBefore0(name, ctx, newCtx);
-        }
+        AbstractChannelHandlerContext ctx = getContextOrDie(baseName);
+        checkDuplicateName(name);
+        AbstractChannelHandlerContext newCtx = newContext(group, name, handler);
+        addBefore0(ctx, newCtx);
         return this;
     }
 
-    private void addBefore0(
-            final String name, AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
+    private void addBefore0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
         checkMultiplicity(newCtx);
 
         newCtx.prev = ctx.prev;
@@ -172,8 +177,6 @@
         ctx.prev.next = newCtx;
         ctx.prev = newCtx;
 
-        name2ctx.put(name, newCtx);
-
         callHandlerAdded(newCtx);
     }
 
@@ -183,21 +186,16 @@
     }
 
     @Override
-    public ChannelPipeline addAfter(
+    public synchronized ChannelPipeline addAfter(
             EventExecutorGroup group, String baseName, final String name, ChannelHandler handler) {
-        synchronized (this) {
-            AbstractChannelHandlerContext ctx = getContextOrDie(baseName);
-            checkDuplicateName(name);
-            AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
-
-            addAfter0(name, ctx, newCtx);
-        }
-
+        AbstractChannelHandlerContext ctx = getContextOrDie(baseName);
+        checkDuplicateName(name);
+        AbstractChannelHandlerContext newCtx = newContext(group, name, handler);
+        addAfter0(ctx, newCtx);
         return this;
     }
 
-    private void addAfter0(final String name, AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
-        checkDuplicateName(name);
+    private void addAfter0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
         checkMultiplicity(newCtx);
 
         newCtx.prev = ctx;
@@ -205,8 +203,6 @@
         ctx.next.prev = newCtx;
         ctx.next = newCtx;
 
-        name2ctx.put(name, newCtx);
-
         callHandlerAdded(newCtx);
     }
 
@@ -261,25 +257,22 @@
     }
 
     private String generateName(ChannelHandler handler) {
-        WeakHashMap, String> cache = nameCaches[(int) (Thread.currentThread().getId() % nameCaches.length)];
+        Map, String> cache = nameCaches.get();
         Class handlerType = handler.getClass();
-        String name;
-        synchronized (cache) {
-            name = cache.get(handlerType);
-            if (name == null) {
-                name = generateName0(handlerType);
-                cache.put(handlerType, name);
-            }
+        String name = cache.get(handlerType);
+        if (name == null) {
+            name = generateName0(handlerType);
+            cache.put(handlerType, name);
         }
 
         synchronized (this) {
             // It's not very likely for a user to put more than one handler of the same type, but make sure to avoid
             // any name conflicts.  Note that we don't cache the names generated here.
-            if (name2ctx.containsKey(name)) {
+            if (context0(name) != null) {
                 String baseName = name.substring(0, name.length() - 1); // Strip the trailing '0'.
                 for (int i = 1;; i ++) {
                     String newName = baseName + i;
-                    if (!name2ctx.containsKey(newName)) {
+                    if (context0(newName) == null) {
                         name = newName;
                         break;
                     }
@@ -318,11 +311,11 @@
         Future future;
 
         synchronized (this) {
-            if (!ctx.channel().isRegistered() || ctx.executor().inEventLoop()) {
+            if (!isExecuteLater(ctx)) {
                 remove0(ctx);
                 return ctx;
             } else {
-               future = ctx.executor().submit(new Runnable() {
+               future = ctx.executor().submit(new OneTimeTask() {
                    @Override
                    public void run() {
                        synchronized (DefaultChannelPipeline.this) {
@@ -347,7 +340,6 @@
         AbstractChannelHandlerContext next = ctx.next;
         prev.next = next;
         next.prev = prev;
-        name2ctx.remove(ctx.name());
         callHandlerRemoved(ctx);
     }
 
@@ -398,18 +390,17 @@
                 checkDuplicateName(newName);
             }
 
-            final AbstractChannelHandlerContext newCtx =
-                    new DefaultChannelHandlerContext(this, ctx.executor, newName, newHandler);
+            final AbstractChannelHandlerContext newCtx = newContext(ctx.executor, newName, newHandler);
 
-            if (!newCtx.channel().isRegistered() || newCtx.executor().inEventLoop()) {
-                replace0(ctx, newName, newCtx);
+            if (!isExecuteLater(newCtx)) {
+                replace0(ctx, newCtx);
                 return ctx.handler();
             } else {
-                future = newCtx.executor().submit(new Runnable() {
+                future = newCtx.executor().submit(new OneTimeTask() {
                     @Override
                     public void run() {
                         synchronized (DefaultChannelPipeline.this) {
-                            replace0(ctx, newName, newCtx);
+                            replace0(ctx, newCtx);
                         }
                     }
                 });
@@ -424,8 +415,7 @@
         return ctx.handler();
     }
 
-    private void replace0(AbstractChannelHandlerContext oldCtx, String newName,
-                          AbstractChannelHandlerContext newCtx) {
+    private void replace0(AbstractChannelHandlerContext oldCtx, AbstractChannelHandlerContext newCtx) {
         checkMultiplicity(newCtx);
 
         AbstractChannelHandlerContext prev = oldCtx.prev;
@@ -440,11 +430,6 @@
         prev.next = newCtx;
         next.prev = newCtx;
 
-        if (!oldCtx.name().equals(newName)) {
-            name2ctx.remove(oldCtx.name());
-        }
-        name2ctx.put(newName, newCtx);
-
         // update the reference to the replacement so forward of buffered content will work correctly
         oldCtx.prev = newCtx;
         oldCtx.next = newCtx;
@@ -469,9 +454,9 @@
         }
     }
 
-    private void callHandlerAdded(final ChannelHandlerContext ctx) {
-        if (ctx.channel().isRegistered() && !ctx.executor().inEventLoop()) {
-            ctx.executor().execute(new Runnable() {
+    private void callHandlerAdded(final AbstractChannelHandlerContext ctx) {
+        if (isExecuteLater(ctx)) {
+            ctx.executor().execute(new OneTimeTask() {
                 @Override
                 public void run() {
                     callHandlerAdded0(ctx);
@@ -482,9 +467,14 @@
         callHandlerAdded0(ctx);
     }
 
-    private void callHandlerAdded0(final ChannelHandlerContext ctx) {
+    private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
         try {
-            ctx.handler().handlerAdded(ctx);
+            try {
+                ctx.handler().handlerAdded(ctx);
+            } finally {
+                // handlerAdded(...) method was called.
+                ctx.setHandlerAddedCalled();
+            }
         } catch (Throwable t) {
             boolean removed = false;
             try {
@@ -509,8 +499,8 @@
     }
 
     private void callHandlerRemoved(final AbstractChannelHandlerContext ctx) {
-        if (ctx.channel().isRegistered() && !ctx.executor().inEventLoop()) {
-            ctx.executor().execute(new Runnable() {
+        if (isExecuteLater(ctx)) {
+            ctx.executor().execute(new OneTimeTask() {
                 @Override
                 public void run() {
                     callHandlerRemoved0(ctx);
@@ -532,6 +522,10 @@
         }
     }
 
+    private static boolean isExecuteLater(ChannelHandlerContext ctx) {
+        return ctx.channel().isRegistered() && !ctx.executor().inEventLoop();
+    }
+
     /**
      * Waits for a future to finish.  If the task is interrupted, then the current thread will be interrupted.
      * It is expected that the task performs any appropriate locking.
@@ -622,9 +616,7 @@
             throw new NullPointerException("name");
         }
 
-        synchronized (this) {
-            return name2ctx.get(name);
-        }
+        return context0(name);
     }
 
     @Override
@@ -749,42 +741,43 @@
      * Removes all handlers from the pipeline one by one from tail (exclusive) to head (exclusive) to trigger
      * handlerRemoved().
      *
-     * Note that we traverse up the pipeline ({@link #destroyUp(AbstractChannelHandlerContext)})
-     * before traversing down ({@link #destroyDown(Thread, AbstractChannelHandlerContext)}) so that
+     * Note that we traverse up the pipeline ({@link #destroyUp(AbstractChannelHandlerContext, boolean)})
+     * before traversing down ({@link #destroyDown(Thread, AbstractChannelHandlerContext, boolean)}) so that
      * the handlers are removed after all events are handled.
      *
      * See: https://github.com/netty/netty/issues/3156
      */
     private void destroy() {
-        destroyUp(head.next);
+        destroyUp(head.next, false);
     }
 
-    private void destroyUp(AbstractChannelHandlerContext ctx) {
+    private void destroyUp(AbstractChannelHandlerContext ctx, boolean inEventLoop) {
         final Thread currentThread = Thread.currentThread();
         final AbstractChannelHandlerContext tail = this.tail;
         for (;;) {
             if (ctx == tail) {
-                destroyDown(currentThread, tail.prev);
+                destroyDown(currentThread, tail.prev, inEventLoop);
                 break;
             }
 
             final EventExecutor executor = ctx.executor();
-            if (!executor.inEventLoop(currentThread)) {
+            if (!inEventLoop && !executor.inEventLoop(currentThread)) {
                 final AbstractChannelHandlerContext finalCtx = ctx;
                 executor.execute(new OneTimeTask() {
                     @Override
                     public void run() {
-                        destroyUp(finalCtx);
+                        destroyUp(finalCtx, true);
                     }
                 });
                 break;
             }
 
             ctx = ctx.next;
+            inEventLoop = false;
         }
     }
 
-    private void destroyDown(Thread currentThread, AbstractChannelHandlerContext ctx) {
+    private void destroyDown(Thread currentThread, AbstractChannelHandlerContext ctx, boolean inEventLoop) {
         // We have reached at tail; now traverse backwards.
         final AbstractChannelHandlerContext head = this.head;
         for (;;) {
@@ -793,7 +786,7 @@
             }
 
             final EventExecutor executor = ctx.executor();
-            if (executor.inEventLoop(currentThread)) {
+            if (inEventLoop || executor.inEventLoop(currentThread)) {
                 synchronized (this) {
                     remove0(ctx);
                 }
@@ -802,13 +795,14 @@
                 executor.execute(new OneTimeTask() {
                     @Override
                     public void run() {
-                        destroyDown(Thread.currentThread(), finalCtx);
+                        destroyDown(Thread.currentThread(), finalCtx, true);
                     }
                 });
                 break;
             }
 
             ctx = ctx.prev;
+            inEventLoop = false;
         }
     }
 
@@ -955,11 +949,22 @@
     }
 
     private void checkDuplicateName(String name) {
-        if (name2ctx.containsKey(name)) {
+        if (context0(name) != null) {
             throw new IllegalArgumentException("Duplicate handler name: " + name);
         }
     }
 
+    private AbstractChannelHandlerContext context0(String name) {
+        AbstractChannelHandlerContext context = head.next;
+        while (context != tail) {
+            if (context.name().equals(name)) {
+                return context;
+            }
+            context = context.next;
+        }
+        return null;
+    }
+
     private AbstractChannelHandlerContext getContextOrDie(String name) {
         AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name);
         if (ctx == null) {
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannel.java netty-4.0.34/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannel.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannel.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannel.java	2016-01-29 08:23:46.000000000 +0000
@@ -30,6 +30,7 @@
 import io.netty.channel.DefaultChannelConfig;
 import io.netty.channel.EventLoop;
 import io.netty.util.ReferenceCountUtil;
+import io.netty.util.internal.ObjectUtil;
 import io.netty.util.internal.PlatformDependent;
 import io.netty.util.internal.RecyclableArrayList;
 import io.netty.util.internal.logging.InternalLogger;
@@ -47,12 +48,15 @@
 
     private static final InternalLogger logger = InternalLoggerFactory.getInstance(EmbeddedChannel.class);
 
-    private static final ChannelMetadata METADATA = new ChannelMetadata(false);
+    private static final ChannelMetadata METADATA_NO_DISCONNECT = new ChannelMetadata(false);
+    private static final ChannelMetadata METADATA_DISCONNECT = new ChannelMetadata(true);
 
     private final EmbeddedEventLoop loop = new EmbeddedEventLoop();
-    private final ChannelConfig config = new DefaultChannelConfig(this);
+    private final ChannelMetadata metadata;
+    private final ChannelConfig config;
     private final SocketAddress localAddress = new EmbeddedSocketAddress();
     private final SocketAddress remoteAddress = new EmbeddedSocketAddress();
+
     private Queue inboundMessages;
     private Queue outboundMessages;
     private Throwable lastException;
@@ -64,11 +68,22 @@
      * @param handlers the @link ChannelHandler}s which will be add in the {@link ChannelPipeline}
      */
     public EmbeddedChannel(final ChannelHandler... handlers) {
-        super(null);
+        this(false, handlers);
+    }
 
-        if (handlers == null) {
-            throw new NullPointerException("handlers");
-        }
+    /**
+     * Create a new instance with the channel ID set to the given ID and the pipeline
+     * initialized with the specified handlers.
+     *
+     * @param hasDisconnect {@code false} if this {@link Channel} will delegate {@link #disconnect()}
+     *                      to {@link #close()}, {@link false} otherwise.
+     * @param handlers the {@link ChannelHandler}s which will be add in the {@link ChannelPipeline}
+     */
+    public EmbeddedChannel(boolean hasDisconnect, final ChannelHandler... handlers) {
+        super(null);
+        ObjectUtil.checkNotNull(handlers, "handlers");
+        metadata = hasDisconnect ? METADATA_DISCONNECT : METADATA_NO_DISCONNECT;
+        config = new DefaultChannelConfig(this);
 
         ChannelPipeline p = pipeline();
         p.addLast(new ChannelInitializer() {
@@ -91,7 +106,7 @@
 
     @Override
     public ChannelMetadata metadata() {
-        return METADATA;
+        return metadata;
     }
 
     @Override
@@ -153,7 +168,7 @@
     }
 
     /**
-     * Read data froum the outbound. This may return {@code null} if nothing is readable.
+     * Read data from the outbound. This may return {@code null} if nothing is readable.
      */
     public Object readOutbound() {
         return poll(outboundMessages);
@@ -233,37 +248,35 @@
         return isNotEmpty(inboundMessages) || isNotEmpty(outboundMessages);
     }
 
-    private void finishPendingTasks() {
+    private void finishPendingTasks(boolean cancel) {
         runPendingTasks();
-        // Cancel all scheduled tasks that are left.
-        loop.cancelScheduledTasks();
+        if (cancel) {
+            // Cancel all scheduled tasks that are left.
+            loop.cancelScheduledTasks();
+        }
     }
 
     @Override
     public final ChannelFuture close() {
-        ChannelFuture future = super.close();
-        finishPendingTasks();
-        return future;
+        return close(newPromise());
     }
 
     @Override
     public final ChannelFuture disconnect() {
-        ChannelFuture future = super.disconnect();
-        finishPendingTasks();
-        return future;
+        return disconnect(newPromise());
     }
 
     @Override
     public final ChannelFuture close(ChannelPromise promise) {
         ChannelFuture future = super.close(promise);
-        finishPendingTasks();
+        finishPendingTasks(true);
         return future;
     }
 
     @Override
     public final ChannelFuture disconnect(ChannelPromise promise) {
         ChannelFuture future = super.disconnect(promise);
-        finishPendingTasks();
+        finishPendingTasks(!metadata.hasDisconnect());
         return future;
     }
 
@@ -368,7 +381,9 @@
 
     @Override
     protected void doDisconnect() throws Exception {
-        doClose();
+        if (!metadata.hasDisconnect()) {
+            doClose();
+        }
     }
 
     @Override
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/EventLoop.java netty-4.0.34/transport/src/main/java/io/netty/channel/EventLoop.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/EventLoop.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/EventLoop.java	2016-01-29 08:23:46.000000000 +0000
@@ -20,7 +20,7 @@
 /**
  * Will handle all the I/O operations for a {@link Channel} once registered.
  *
- * One {@link EventLoop} instance will usually handle more then one {@link Channel} but this may depend on
+ * One {@link EventLoop} instance will usually handle more than one {@link Channel} but this may depend on
  * implementation details and internals.
  *
  */
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/local/LocalChannel.java netty-4.0.34/transport/src/main/java/io/netty/channel/local/LocalChannel.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/local/LocalChannel.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/local/LocalChannel.java	2016-01-29 08:23:46.000000000 +0000
@@ -194,8 +194,13 @@
                 @Override
                 public void run() {
                     registerInProgress = false;
-                    peer.pipeline().fireChannelActive();
-                    peer.connectPromise.setSuccess();
+                    ChannelPromise promise = peer.connectPromise;
+
+                    // Only trigger fireChannelActive() if the promise was not null and was not completed yet.
+                    // connectPromise may be set to null if doClose() was called in the meantime.
+                    if (promise != null && promise.trySuccess()) {
+                        peer.pipeline().fireChannelActive();
+                    }
                 }
             });
         }
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java netty-4.0.34/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java	2016-01-29 08:23:46.000000000 +0000
@@ -476,11 +476,11 @@
                 // null out entries in the array to allow to have it GC'ed once the Channel close
                 // See https://github.com/netty/netty/issues/2363
                 for (;;) {
+                    i++;
                     if (selectedKeys[i] == null) {
                         break;
                     }
                     selectedKeys[i] = null;
-                    i++;
                 }
 
                 selectAgain();
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannel.java netty-4.0.34/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannel.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannel.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannel.java	2016-01-29 08:23:46.000000000 +0000
@@ -80,7 +80,7 @@
              *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
              *  {@link SelectorProvider#provider()} which is called by each DatagramChannel.open() otherwise.
              *
-             *  See #2308.
+             *  See #2308.
              */
             return provider.openDatagramChannel();
         } catch (IOException e) {
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java netty-4.0.34/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketChannel.java	2016-01-29 08:23:46.000000000 +0000
@@ -52,7 +52,7 @@
              *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
              *  {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
              *
-             *  See #2308.
+             *  See #2308.
              */
             return provider.openServerSocketChannel();
         } catch (IOException e) {
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java netty-4.0.34/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java	2016-01-29 08:23:46.000000000 +0000
@@ -55,7 +55,7 @@
              *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
              *  {@link SelectorProvider#provider()} which is called by each SocketChannel.open() otherwise.
              *
-             *  See #2308.
+             *  See #2308.
              */
             return provider.openSocketChannel();
         } catch (IOException e) {
@@ -150,7 +150,7 @@
 
     @Override
     public ChannelFuture shutdownOutput(final ChannelPromise promise) {
-        Executor closeExecutor = ((NioSocketChannelUnsafe) unsafe()).closeExecutor();
+        Executor closeExecutor = ((NioSocketChannelUnsafe) unsafe()).prepareToClose();
         if (closeExecutor != null) {
             closeExecutor.execute(new OneTimeTask() {
                 @Override
@@ -332,9 +332,20 @@
 
     private final class NioSocketChannelUnsafe extends NioByteUnsafe {
         @Override
-        protected Executor closeExecutor() {
-            if (javaChannel().isOpen() && config().getSoLinger() > 0) {
-                return GlobalEventExecutor.INSTANCE;
+        protected Executor prepareToClose() {
+            try {
+                if (javaChannel().isOpen() && config().getSoLinger() > 0) {
+                    // We need to cancel this key of the channel so we may not end up in a eventloop spin
+                    // because we try to read or write until the actual close happens which may be later due
+                    // SO_LINGER handling.
+                    // See https://github.com/netty/netty/issues/4449
+                    doDeregister();
+                    return GlobalEventExecutor.INSTANCE;
+                }
+            } catch (Throwable ignore) {
+                // Ignore the error as the underlying channel may be closed in the meantime and so
+                // getSoLinger() may produce an exception. In this case we just return null.
+                // See https://github.com/netty/netty/issues/4449
             }
             return null;
         }
diff -Nru netty-4.0.33/transport/src/main/java/io/netty/channel/socket/oio/OioSocketChannel.java netty-4.0.34/transport/src/main/java/io/netty/channel/socket/oio/OioSocketChannel.java
--- netty-4.0.33/transport/src/main/java/io/netty/channel/socket/oio/OioSocketChannel.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/main/java/io/netty/channel/socket/oio/OioSocketChannel.java	2016-01-29 08:23:46.000000000 +0000
@@ -25,6 +25,7 @@
 import io.netty.channel.oio.OioByteStreamChannel;
 import io.netty.channel.socket.ServerSocketChannel;
 import io.netty.channel.socket.SocketChannel;
+import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
 
@@ -152,7 +153,7 @@
                 future.setFailure(t);
             }
         } else {
-            loop.execute(new Runnable() {
+            loop.execute(new OneTimeTask() {
                 @Override
                 public void run() {
                     shutdownOutput(future);
diff -Nru netty-4.0.33/transport/src/test/java/io/netty/channel/AbstractChannelTest.java netty-4.0.34/transport/src/test/java/io/netty/channel/AbstractChannelTest.java
--- netty-4.0.33/transport/src/test/java/io/netty/channel/AbstractChannelTest.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/test/java/io/netty/channel/AbstractChannelTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -15,20 +15,14 @@
  */
 package io.netty.channel;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-
 import java.net.SocketAddress;
 
 import org.easymock.Capture;
+import org.easymock.IAnswer;
 import org.junit.Test;
 
+import static org.easymock.EasyMock.*;
+
 public class AbstractChannelTest {
 
     @Test
@@ -56,43 +50,56 @@
 
     @Test
     public void ensureSubsequentRegistrationDoesNotFireActive() throws Throwable {
-            EventLoop eventLoop = createNiceMock(EventLoop.class);
-            // This allows us to have a single-threaded test
-            expect(eventLoop.inEventLoop()).andReturn(true).anyTimes();
-
-            TestChannel channel = new TestChannel();
-            ChannelInboundHandler handler = createMock(ChannelInboundHandler.class);
-            handler.handlerAdded(anyObject(ChannelHandlerContext.class)); expectLastCall();
-            Capture throwable = catchHandlerExceptions(handler);
-            handler.channelRegistered(anyObject(ChannelHandlerContext.class));
-            expectLastCall().times(2); // Should register twice
-            handler.channelActive(anyObject(ChannelHandlerContext.class));
-            expectLastCall().once(); // Should only fire active once
-            replay(handler, eventLoop);
-            channel.pipeline().addLast(handler);
-
-            registerChannel(eventLoop, channel);
-            channel.unsafe().deregister(new DefaultChannelPromise(channel));
-            registerChannel(eventLoop, channel);
+        final EventLoop eventLoop = createNiceMock(EventLoop.class);
+        // This allows us to have a single-threaded test
+        expect(eventLoop.inEventLoop()).andReturn(true).anyTimes();
+        eventLoop.execute(anyObject(Runnable.class));
+        expectLastCall().andAnswer(new IAnswer() {
+            @Override
+            public Object answer() throws Throwable {
+                ((Runnable) getCurrentArguments()[0]).run();
+                return null;
+            }
+        }).once();
 
-            checkForHandlerException(throwable);
-            verify(handler);
-        }
+        final TestChannel channel = new TestChannel();
+        ChannelInboundHandler handler = createMock(ChannelInboundHandler.class);
+        handler.handlerAdded(anyObject(ChannelHandlerContext.class)); expectLastCall();
+        Capture throwable = catchHandlerExceptions(handler);
+        handler.channelRegistered(anyObject(ChannelHandlerContext.class));
+        expectLastCall().times(2); // Should register twice
+        handler.channelActive(anyObject(ChannelHandlerContext.class));
+        expectLastCall().once(); // Should only fire active once
+
+        handler.channelUnregistered(anyObject(ChannelHandlerContext.class));
+        expectLastCall().once(); // Should register twice
+
+        replay(handler, eventLoop);
+        channel.pipeline().addLast(handler);
+
+        registerChannel(eventLoop, channel);
+        channel.unsafe().deregister(new DefaultChannelPromise(channel));
+
+        registerChannel(eventLoop, channel);
+
+        checkForHandlerException(throwable);
+        verify(handler);
+    }
 
-    private void registerChannel(EventLoop eventLoop, Channel channel) throws Exception {
+    private static void registerChannel(EventLoop eventLoop, Channel channel) throws Exception {
         DefaultChannelPromise future = new DefaultChannelPromise(channel);
         channel.unsafe().register(eventLoop, future);
         future.sync(); // Cause any exceptions to be thrown
     }
 
-    private Capture catchHandlerExceptions(ChannelInboundHandler handler) throws Exception {
+    private static Capture catchHandlerExceptions(ChannelInboundHandler handler) throws Exception {
         Capture throwable = new Capture();
         handler.exceptionCaught(anyObject(ChannelHandlerContext.class), capture(throwable));
         expectLastCall().anyTimes();
         return throwable;
     }
 
-    private void checkForHandlerException(Capture throwable) throws Throwable {
+    private static void checkForHandlerException(Capture throwable) throws Throwable {
         if (throwable.hasCaptured()) {
             throw throwable.getValue();
         }
diff -Nru netty-4.0.33/transport/src/test/java/io/netty/channel/CombinedChannelDuplexHandlerTest.java netty-4.0.34/transport/src/test/java/io/netty/channel/CombinedChannelDuplexHandlerTest.java
--- netty-4.0.33/transport/src/test/java/io/netty/channel/CombinedChannelDuplexHandlerTest.java	1970-01-01 00:00:00.000000000 +0000
+++ netty-4.0.34/transport/src/test/java/io/netty/channel/CombinedChannelDuplexHandlerTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2016 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.channel;
+
+import io.netty.channel.embedded.EmbeddedChannel;
+import org.junit.Test;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+import static org.junit.Assert.*;
+
+public class CombinedChannelDuplexHandlerTest {
+
+    private static final Object MSG = new Object();
+    private static final SocketAddress ADDRESS = new InetSocketAddress(0);
+
+    private enum Event {
+        REGISTERED,
+        UNREGISTERED,
+        ACTIVE,
+        INACTIVE,
+        CHANNEL_READ,
+        CHANNEL_READ_COMPLETE,
+        EXCEPTION_CAUGHT,
+        USER_EVENT_TRIGGERED,
+        CHANNEL_WRITABILITY_CHANGED,
+        HANDLER_ADDED,
+        HANDLER_REMOVED,
+        BIND,
+        CONNECT,
+        WRITE,
+        FLUSH,
+        READ,
+        REGISTER,
+        DEREGISTER,
+        CLOSE,
+        DISCONNECT
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testInboundRemoveBeforeAdded() {
+        CombinedChannelDuplexHandler handler =
+                new CombinedChannelDuplexHandler(
+                        new ChannelInboundHandlerAdapter(), new ChannelOutboundHandlerAdapter());
+        handler.removeInboundHandler();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testOutboundRemoveBeforeAdded() {
+        CombinedChannelDuplexHandler handler =
+                new CombinedChannelDuplexHandler(
+                        new ChannelInboundHandlerAdapter(), new ChannelOutboundHandlerAdapter());
+        handler.removeOutboundHandler();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInboundHandlerImplementsOutboundHandler() {
+        new CombinedChannelDuplexHandler(
+                new ChannelDuplexHandler(), new ChannelOutboundHandlerAdapter());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testOutboundHandlerImplementsInbboundHandler() {
+        new CombinedChannelDuplexHandler(
+                new ChannelInboundHandlerAdapter(), new ChannelDuplexHandler());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testInitNotCalledBeforeAdded() throws Exception {
+        CombinedChannelDuplexHandler handler =
+                new CombinedChannelDuplexHandler() { };
+        handler.handlerAdded(null);
+    }
+
+    @Test
+    public void testExceptionCaughtBothCombinedHandlers() {
+        final Exception exception = new Exception();
+        final Queue queue = new ArrayDeque();
+
+        ChannelInboundHandler inboundHandler = new ChannelInboundHandlerAdapter() {
+            @Override
+            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+                assertSame(exception, cause);
+                queue.add(this);
+                ctx.fireExceptionCaught(cause);
+            }
+        };
+        ChannelOutboundHandler outboundHandler = new ChannelOutboundHandlerAdapter() {
+            @Override
+            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+                assertSame(exception, cause);
+                queue.add(this);
+                ctx.fireExceptionCaught(cause);
+            }
+        };
+        ChannelInboundHandler lastHandler = new ChannelInboundHandlerAdapter() {
+            @Override
+            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+                assertSame(exception, cause);
+                queue.add(this);
+            }
+        };
+        EmbeddedChannel channel = new EmbeddedChannel(
+                new CombinedChannelDuplexHandler(
+                        inboundHandler, outboundHandler), lastHandler);
+        channel.pipeline().fireExceptionCaught(exception);
+        assertFalse(channel.finish());
+        assertSame(inboundHandler, queue.poll());
+        assertSame(outboundHandler, queue.poll());
+        assertSame(lastHandler, queue.poll());
+        assertTrue(queue.isEmpty());
+    }
+
+    @Test
+    public void testInboundEvents() {
+        final Queue queue = new ArrayDeque();
+
+        ChannelInboundHandler inboundHandler = new ChannelInboundHandlerAdapter() {
+            @Override
+            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.HANDLER_ADDED);
+            }
+
+            @Override
+            public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.HANDLER_REMOVED);
+            }
+
+            @Override
+            public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.REGISTERED);
+            }
+
+            @Override
+            public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.UNREGISTERED);
+            }
+
+            @Override
+            public void channelActive(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.ACTIVE);
+            }
+
+            @Override
+            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.INACTIVE);
+            }
+
+            @Override
+            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+                queue.add(Event.CHANNEL_READ);
+            }
+
+            @Override
+            public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.CHANNEL_READ_COMPLETE);
+            }
+
+            @Override
+            public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+                queue.add(Event.USER_EVENT_TRIGGERED);
+            }
+
+            @Override
+            public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.CHANNEL_WRITABILITY_CHANGED);
+            }
+
+            @Override
+            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+                queue.add(Event.EXCEPTION_CAUGHT);
+            }
+        };
+
+        CombinedChannelDuplexHandler handler =
+                new CombinedChannelDuplexHandler(
+                inboundHandler, new ChannelOutboundHandlerAdapter());
+
+        EmbeddedChannel channel = new EmbeddedChannel(handler);
+        channel.pipeline().fireChannelWritabilityChanged();
+        channel.pipeline().fireUserEventTriggered(MSG);
+        channel.pipeline().fireChannelRead(MSG);
+        channel.pipeline().fireChannelReadComplete();
+
+        assertEquals(Event.HANDLER_ADDED, queue.poll());
+        assertEquals(Event.REGISTERED, queue.poll());
+        assertEquals(Event.ACTIVE, queue.poll());
+        assertEquals(Event.CHANNEL_WRITABILITY_CHANGED, queue.poll());
+        assertEquals(Event.USER_EVENT_TRIGGERED, queue.poll());
+        assertEquals(Event.CHANNEL_READ, queue.poll());
+        assertEquals(Event.CHANNEL_READ_COMPLETE, queue.poll());
+
+        handler.removeInboundHandler();
+        assertEquals(Event.HANDLER_REMOVED, queue.poll());
+
+        // These should not be handled by the inboundHandler anymore as it was removed before
+        channel.pipeline().fireChannelWritabilityChanged();
+        channel.pipeline().fireUserEventTriggered(MSG);
+        channel.pipeline().fireChannelRead(MSG);
+        channel.pipeline().fireChannelReadComplete();
+
+        // Should have not received any more events as it was removed before via removeInboundHandler()
+        assertTrue(queue.isEmpty());
+        assertTrue(channel.finish());
+        assertTrue(queue.isEmpty());
+    }
+
+    @Test
+    public void testOutboundEvents() {
+        final Queue queue = new ArrayDeque();
+
+        ChannelInboundHandler inboundHandler = new ChannelInboundHandlerAdapter();
+        ChannelOutboundHandler outboundHandler = new ChannelOutboundHandlerAdapter() {
+            @Override
+            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.HANDLER_ADDED);
+            }
+
+            @Override
+            public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.HANDLER_REMOVED);
+            }
+
+            @Override
+            public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
+                    throws Exception {
+                queue.add(Event.BIND);
+            }
+
+            @Override
+            public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
+                                SocketAddress localAddress, ChannelPromise promise) throws Exception {
+                queue.add(Event.CONNECT);
+            }
+
+            @Override
+            public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+                queue.add(Event.DISCONNECT);
+            }
+
+            @Override
+            public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+                queue.add(Event.CLOSE);
+            }
+
+            @Override
+            public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+                queue.add(Event.DEREGISTER);
+            }
+
+            @Override
+            public void read(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.READ);
+            }
+
+            @Override
+            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+                queue.add(Event.WRITE);
+            }
+
+            @Override
+            public void flush(ChannelHandlerContext ctx) throws Exception {
+                queue.add(Event.FLUSH);
+            }
+        };
+
+        CombinedChannelDuplexHandler handler =
+                new CombinedChannelDuplexHandler(
+                        inboundHandler, outboundHandler);
+
+        EmbeddedChannel channel = new EmbeddedChannel();
+        channel.pipeline().addFirst(handler);
+
+        doOutboundOperations(channel);
+
+        assertEquals(Event.HANDLER_ADDED, queue.poll());
+        assertEquals(Event.BIND, queue.poll());
+        assertEquals(Event.CONNECT, queue.poll());
+        assertEquals(Event.WRITE, queue.poll());
+        assertEquals(Event.FLUSH, queue.poll());
+        assertEquals(Event.READ, queue.poll());
+        assertEquals(Event.CLOSE, queue.poll());
+        assertEquals(Event.CLOSE, queue.poll());
+        assertEquals(Event.DEREGISTER, queue.poll());
+
+        handler.removeOutboundHandler();
+        assertEquals(Event.HANDLER_REMOVED, queue.poll());
+
+        // These should not be handled by the inboundHandler anymore as it was removed before
+        doOutboundOperations(channel);
+
+        // Should have not received any more events as it was removed before via removeInboundHandler()
+        assertTrue(queue.isEmpty());
+        assertTrue(channel.finish());
+        assertTrue(queue.isEmpty());
+    }
+
+    private static void doOutboundOperations(Channel channel) {
+        channel.pipeline().bind(ADDRESS);
+        channel.pipeline().connect(ADDRESS);
+        channel.pipeline().write(MSG);
+        channel.pipeline().flush();
+        channel.pipeline().read();
+        channel.pipeline().disconnect();
+        channel.pipeline().close();
+        channel.pipeline().deregister();
+    }
+}
diff -Nru netty-4.0.33/transport/src/test/java/io/netty/channel/DefaultChannelPipelineTest.java netty-4.0.34/transport/src/test/java/io/netty/channel/DefaultChannelPipelineTest.java
--- netty-4.0.33/transport/src/test/java/io/netty/channel/DefaultChannelPipelineTest.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/test/java/io/netty/channel/DefaultChannelPipelineTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -29,6 +29,9 @@
 import io.netty.util.AbstractReferenceCounted;
 import io.netty.util.ReferenceCountUtil;
 import io.netty.util.ReferenceCounted;
+import io.netty.util.concurrent.AbstractEventExecutor;
+import io.netty.util.concurrent.EventExecutorGroup;
+import io.netty.util.concurrent.Future;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Test;
@@ -39,6 +42,8 @@
 import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -559,6 +564,37 @@
         assertSame(exception, error.get());
     }
 
+    @Test
+    public void testChannelUnregistrationWithCustomExecutor() throws Exception {
+        final CountDownLatch channelLatch = new CountDownLatch(1);
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        ChannelPipeline pipeline = new LocalChannel().pipeline();
+        pipeline.addLast(new ChannelInitializer() {
+            @Override
+            protected void initChannel(Channel ch) throws Exception {
+                ch.pipeline().addLast(new WrapperExecutor(),
+                        new ChannelInboundHandlerAdapter() {
+
+                            @Override
+                            public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
+                                channelLatch.countDown();
+                            }
+
+                            @Override
+                            public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+                                handlerLatch.countDown();
+                            }
+                        });
+            }
+        });
+        Channel channel = pipeline.channel();
+        group.register(channel);
+        channel.close();
+        channel.deregister();
+        assertTrue(channelLatch.await(2, TimeUnit.SECONDS));
+        assertTrue(handlerLatch.await(2, TimeUnit.SECONDS));
+    }
+
     private static int next(AbstractChannelHandlerContext ctx) {
         AbstractChannelHandlerContext next = ctx.next;
         if (next == null) {
@@ -670,4 +706,64 @@
             afterRemove = true;
         }
     }
+
+    private static final class WrapperExecutor extends AbstractEventExecutor {
+
+        private final ExecutorService wrapped = Executors.newSingleThreadExecutor();
+
+        @Override
+        public boolean isShuttingDown() {
+            return wrapped.isShutdown();
+        }
+
+        @Override
+        public Future shutdownGracefully(long l, long l2, TimeUnit timeUnit) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Future terminationFuture() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void shutdown() {
+            wrapped.shutdown();
+        }
+
+        @Override
+        public List shutdownNow() {
+            return wrapped.shutdownNow();
+        }
+
+        @Override
+        public boolean isShutdown() {
+            return wrapped.isShutdown();
+        }
+
+        @Override
+        public boolean isTerminated() {
+            return wrapped.isTerminated();
+        }
+
+        @Override
+        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+            return wrapped.awaitTermination(timeout, unit);
+        }
+
+        @Override
+        public EventExecutorGroup parent() {
+            return null;
+        }
+
+        @Override
+        public boolean inEventLoop(Thread thread) {
+            return false;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            wrapped.execute(command);
+        }
+    }
 }
diff -Nru netty-4.0.33/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelTest.java netty-4.0.34/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelTest.java
--- netty-4.0.33/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelTest.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -22,17 +22,22 @@
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOutboundHandlerAdapter;
 import io.netty.channel.ChannelPipeline;
+import io.netty.channel.ChannelPromise;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.FutureListener;
 import io.netty.util.concurrent.ScheduledFuture;
-import org.junit.Assert;
 import org.junit.Test;
 
+import java.util.ArrayDeque;
+import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
+import static org.junit.Assert.*;
+
 public class EmbeddedChannelTest {
 
     @Test
@@ -54,12 +59,12 @@
             }
         });
         ChannelPipeline pipeline = channel.pipeline();
-        Assert.assertSame(handler, pipeline.firstContext().handler());
-        Assert.assertTrue(channel.writeInbound(3));
-        Assert.assertTrue(channel.finish());
-        Assert.assertSame(first, channel.readInbound());
-        Assert.assertSame(second, channel.readInbound());
-        Assert.assertNull(channel.readInbound());
+        assertSame(handler, pipeline.firstContext().handler());
+        assertTrue(channel.writeInbound(3));
+        assertTrue(channel.finish());
+        assertSame(first, channel.readInbound());
+        assertSame(second, channel.readInbound());
+        assertNull(channel.readInbound());
     }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
@@ -80,11 +85,11 @@
             }
         });
         long next = ch.runScheduledPendingTasks();
-        Assert.assertTrue(next > 0);
+        assertTrue(next > 0);
         // Sleep for the nanoseconds but also give extra 50ms as the clock my not be very precise and so fail the test
         // otherwise.
         Thread.sleep(TimeUnit.NANOSECONDS.toMillis(next) + 50);
-        Assert.assertEquals(-1, ch.runScheduledPendingTasks());
+        assertEquals(-1, ch.runScheduledPendingTasks());
         latch.await();
     }
 
@@ -96,7 +101,7 @@
             public void run() { }
         }, 1, TimeUnit.DAYS);
         ch.finish();
-        Assert.assertTrue(future.isCancelled());
+        assertTrue(future.isCancelled());
     }
 
     @Test(timeout = 3000)
@@ -107,7 +112,7 @@
             @Override
             public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
                 try {
-                    Assert.assertTrue(ctx.executor().inEventLoop());
+                    assertTrue(ctx.executor().inEventLoop());
                 } catch (Throwable cause) {
                     error.set(cause);
                 } finally {
@@ -116,7 +121,7 @@
             }
         };
         EmbeddedChannel channel = new EmbeddedChannel(handler);
-        Assert.assertFalse(channel.finish());
+        assertFalse(channel.finish());
         latch.await();
         Throwable cause = error.get();
         if (cause != null) {
@@ -127,13 +132,13 @@
     @Test
     public void testConstructWithOutHandler() {
         EmbeddedChannel channel = new EmbeddedChannel();
-        Assert.assertTrue(channel.writeInbound(1));
-        Assert.assertTrue(channel.writeOutbound(2));
-        Assert.assertTrue(channel.finish());
-        Assert.assertSame(1, channel.readInbound());
-        Assert.assertNull(channel.readInbound());
-        Assert.assertSame(2, channel.readOutbound());
-        Assert.assertNull(channel.readOutbound());
+        assertTrue(channel.writeInbound(1));
+        assertTrue(channel.writeOutbound(2));
+        assertTrue(channel.finish());
+        assertSame(1, channel.readInbound());
+        assertNull(channel.readInbound());
+        assertSame(2, channel.readOutbound());
+        assertNull(channel.readOutbound());
     }
 
     // See https://github.com/netty/netty/issues/4316.
@@ -197,4 +202,49 @@
     private interface Action {
         ChannelFuture doRun(Channel channel);
     }
+
+    @Test
+    public void testHasDisconnect() {
+        EventOutboundHandler handler = new EventOutboundHandler();
+        EmbeddedChannel channel = new EmbeddedChannel(true, handler);
+        assertTrue(channel.disconnect().isSuccess());
+        assertTrue(channel.close().isSuccess());
+        assertEquals(EventOutboundHandler.DISCONNECT, handler.pollEvent());
+        assertEquals(EventOutboundHandler.CLOSE, handler.pollEvent());
+        assertNull(handler.pollEvent());
+    }
+
+    @Test
+    public void testHasNoDisconnect() {
+        EventOutboundHandler handler = new EventOutboundHandler();
+        EmbeddedChannel channel = new EmbeddedChannel(false, handler);
+        assertTrue(channel.disconnect().isSuccess());
+        assertTrue(channel.close().isSuccess());
+        assertEquals(EventOutboundHandler.CLOSE, handler.pollEvent());
+        assertEquals(EventOutboundHandler.CLOSE, handler.pollEvent());
+        assertNull(handler.pollEvent());
+    }
+
+    private static final class EventOutboundHandler extends ChannelOutboundHandlerAdapter {
+        static final Integer DISCONNECT = 0;
+        static final Integer CLOSE = 1;
+
+        private final Queue queue = new ArrayDeque();
+
+        @Override
+        public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+            queue.add(DISCONNECT);
+            promise.setSuccess();
+        }
+
+        @Override
+        public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+            queue.add(CLOSE);
+            promise.setSuccess();
+        }
+
+        Integer pollEvent() {
+            return queue.poll();
+        }
+    }
 }
diff -Nru netty-4.0.33/transport/src/test/java/io/netty/channel/local/LocalChannelTest.java netty-4.0.34/transport/src/test/java/io/netty/channel/local/LocalChannelTest.java
--- netty-4.0.33/transport/src/test/java/io/netty/channel/local/LocalChannelTest.java	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport/src/test/java/io/netty/channel/local/LocalChannelTest.java	2016-01-29 08:23:46.000000000 +0000
@@ -33,6 +33,7 @@
 import io.netty.util.ReferenceCountUtil;
 import io.netty.util.concurrent.EventExecutor;
 import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.Promise;
 import io.netty.util.internal.OneTimeTask;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
@@ -810,6 +811,57 @@
         }
     }
 
+    @Test(timeout = 3000)
+    public void testConnectFutureBeforeChannelActive() throws Exception {
+        Bootstrap cb = new Bootstrap();
+        ServerBootstrap sb = new ServerBootstrap();
+
+        cb.group(group1)
+                .channel(LocalChannel.class)
+                .handler(new ChannelInboundHandlerAdapter());
+
+        sb.group(group2)
+                .channel(LocalServerChannel.class)
+                .childHandler(new ChannelInitializer() {
+                    @Override
+                    public void initChannel(LocalChannel ch) throws Exception {
+                        ch.pipeline().addLast(new TestHandler());
+                    }
+                });
+
+        Channel sc = null;
+        Channel cc = null;
+        try {
+            // Start server
+            sc = sb.bind(TEST_ADDRESS).sync().channel();
+
+            cc = cb.register().sync().channel();
+
+            final ChannelPromise promise = cc.newPromise();
+            final Promise assertPromise = cc.eventLoop().newPromise();
+
+            cc.pipeline().addLast(new TestHandler() {
+                @Override
+                public void channelActive(ChannelHandlerContext ctx) throws Exception {
+                    // Ensure the promise was done before the handler method is triggered.
+                    if (promise.isDone()) {
+                        assertPromise.setSuccess(null);
+                    } else {
+                        assertPromise.setFailure(new AssertionError("connect promise should be done"));
+                    }
+                }
+            });
+            // Connect to the server
+            cc.connect(sc.localAddress(), promise).sync();
+
+            assertPromise.syncUninterruptibly();
+            assertTrue(promise.isSuccess());
+        } finally {
+            closeChannel(cc);
+            closeChannel(sc);
+        }
+    }
+
     private static final class LatchChannelFutureListener extends CountDownLatch implements ChannelFutureListener {
         public LatchChannelFutureListener(int count) {
             super(count);
diff -Nru netty-4.0.33/transport-native-epoll/pom.xml netty-4.0.34/transport-native-epoll/pom.xml
--- netty-4.0.33/transport-native-epoll/pom.xml	2015-11-03 13:18:17.000000000 +0000
+++ netty-4.0.34/transport-native-epoll/pom.xml	2016-01-29 08:23:46.000000000 +0000
@@ -1,4 +1,4 @@
-
+