diff --git a/codec/src/main/java/io/netty/handler/codec/base64/Base64.java b/codec/src/main/java/io/netty/handler/codec/base64/Base64.java index dc3f1aff80..770940854b 100644 --- a/codec/src/main/java/io/netty/handler/codec/base64/Base64.java +++ b/codec/src/main/java/io/netty/handler/codec/base64/Base64.java @@ -21,6 +21,11 @@ package io.netty.handler.codec.base64; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.netty.util.ByteProcessor; +import io.netty.util.internal.PlatformDependent; + +import java.nio.ByteOrder; /** * Utility class for {@link ByteBuf} that encodes and decodes to and from @@ -109,7 +114,6 @@ public final class Base64 { public static ByteBuf encode( ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect, ByteBufAllocator allocator) { - if (src == null) { throw new NullPointerException("src"); } @@ -117,17 +121,18 @@ public final class Base64 { throw new NullPointerException("dialect"); } - int len43 = len * 4 / 3; + int len43 = (len << 2) / 3; ByteBuf dest = allocator.buffer( len43 + (len % 3 > 0 ? 4 : 0) + // Account for padding (breakLines ? len43 / MAX_LINE_LENGTH : 0)).order(src.order()); // New lines + byte[] alphabet = alphabet(dialect); int d = 0; int e = 0; int len2 = len - 2; int lineLength = 0; for (; d < len2; d += 3, e += 4) { - encode3to4(src, d + off, 3, dest, e, dialect); + encode3to4(src, d + off, 3, dest, e, alphabet); lineLength += 4; @@ -139,7 +144,7 @@ public final class Base64 { } // end for: each piece of array if (d < len) { - encode3to4(src, d + off, len - d, dest, e, dialect); + encode3to4(src, d + off, len - d, dest, e, alphabet); e += 4; } // end if: some padding needed @@ -152,11 +157,7 @@ public final class Base64 { } private static void encode3to4( - ByteBuf src, int srcOffset, int numSigBytes, - ByteBuf dest, int destOffset, Base64Dialect dialect) { - - byte[] ALPHABET = alphabet(dialect); - + ByteBuf src, int srcOffset, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) { // 1 2 3 // 01234567890123456789012345678901 Bit position // --------000000001111111122222222 Array position from threeBytes @@ -168,30 +169,110 @@ public final class Base64 { // significant bytes passed in the array. // We have to shift left 24 in order to flush out the 1's that appear // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = - (numSigBytes > 0? src.getByte(srcOffset) << 24 >>> 8 : 0) | - (numSigBytes > 1? src.getByte(srcOffset + 1) << 24 >>> 16 : 0) | - (numSigBytes > 2? src.getByte(srcOffset + 2) << 24 >>> 24 : 0); + if (src.order() == ByteOrder.BIG_ENDIAN) { + final int inBuff; + switch (numSigBytes) { + case 1: + inBuff = toInt(src.getByte(srcOffset)); + break; + case 2: + inBuff = toIntBE(src.getShort(srcOffset)); + break; + default: + inBuff = numSigBytes <= 0 ? 0 : toIntBE(src.getMedium(srcOffset)); + break; + } + encode3to4BigEndian(inBuff, numSigBytes, dest, destOffset, alphabet); + } else { + final int inBuff; + switch (numSigBytes) { + case 1: + inBuff = toInt(src.getByte(srcOffset)); + break; + case 2: + inBuff = toIntLE(src.getShort(srcOffset)); + break; + default: + inBuff = numSigBytes <= 0 ? 0 : toIntLE(src.getMedium(srcOffset)); + break; + } + encode3to4LittleEndian(inBuff, numSigBytes, dest, destOffset, alphabet); + } + } + private static int toInt(byte value) { + return (value & 0xff) << 16; + } + + private static int toIntBE(short value) { + return (value & 0xff00) << 8 | (value & 0xff) << 8; + } + + private static int toIntLE(short value) { + return (value & 0xff) << 16 | (value & 0xff00); + } + + private static int toIntBE(int mediumValue) { + return (mediumValue & 0xff0000) | (mediumValue & 0xff00) | (mediumValue & 0xff); + } + + private static int toIntLE(int mediumValue) { + return (mediumValue & 0xff) << 16 | (mediumValue & 0xff00) | (mediumValue & 0xff0000) >>> 16; + } + + private static void encode3to4BigEndian( + int inBuff, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) { + // Packing bytes into an int to reduce bound and reference count checking. switch (numSigBytes) { - case 3: - dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]); - dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]); - dest.setByte(destOffset + 2, ALPHABET[inBuff >>> 6 & 0x3f]); - dest.setByte(destOffset + 3, ALPHABET[inBuff & 0x3f]); - break; - case 2: - dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]); - dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]); - dest.setByte(destOffset + 2, ALPHABET[inBuff >>> 6 & 0x3f]); - dest.setByte(destOffset + 3, EQUALS_SIGN); - break; - case 1: - dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]); - dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]); - dest.setByte(destOffset + 2, EQUALS_SIGN); - dest.setByte(destOffset + 3, EQUALS_SIGN); - break; + case 3: + dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 | + alphabet[inBuff >>> 12 & 0x3f] << 16 | + alphabet[inBuff >>> 6 & 0x3f] << 8 | + alphabet[inBuff & 0x3f]); + break; + case 2: + dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 | + alphabet[inBuff >>> 12 & 0x3f] << 16 | + alphabet[inBuff >>> 6 & 0x3f] << 8 | + EQUALS_SIGN); + break; + case 1: + dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 | + alphabet[inBuff >>> 12 & 0x3f] << 16 | + EQUALS_SIGN << 8 | + EQUALS_SIGN); + break; + default: + // NOOP + break; + } + } + + private static void encode3to4LittleEndian( + int inBuff, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) { + // Packing bytes into an int to reduce bound and reference count checking. + switch (numSigBytes) { + case 3: + dest.setInt(destOffset, alphabet[inBuff >>> 18 ] | + alphabet[inBuff >>> 12 & 0x3f] << 8 | + alphabet[inBuff >>> 6 & 0x3f] << 16 | + alphabet[inBuff & 0x3f] << 24); + break; + case 2: + dest.setInt(destOffset, alphabet[inBuff >>> 18 ] | + alphabet[inBuff >>> 12 & 0x3f] << 8 | + alphabet[inBuff >>> 6 & 0x3f] << 16 | + EQUALS_SIGN << 24); + break; + case 1: + dest.setInt(destOffset, alphabet[inBuff >>> 18 ] | + alphabet[inBuff >>> 12 & 0x3f] << 8 | + EQUALS_SIGN << 16 | + EQUALS_SIGN << 24); + break; + default: + // NOOP + break; } } @@ -200,7 +281,6 @@ public final class Base64 { } public static ByteBuf decode(ByteBuf src, Base64Dialect dialect) { - if (src == null) { throw new NullPointerException("src"); } @@ -222,7 +302,6 @@ public final class Base64 { public static ByteBuf decode( ByteBuf src, int off, int len, Base64Dialect dialect, ByteBufAllocator allocator) { - if (src == null) { throw new NullPointerException("src"); } @@ -230,85 +309,96 @@ public final class Base64 { throw new NullPointerException("dialect"); } - byte[] DECODABET = decodabet(dialect); + // Using a ByteProcessor to reduce bound and reference count checking. + return new Decoder().decode(src, off, len, allocator, dialect); + } - int len34 = len * 3 / 4; - ByteBuf dest = allocator.buffer(len34).order(src.order()); // Upper limit on size of output - int outBuffPosn = 0; + private static final class Decoder implements ByteProcessor { + private final byte[] b4 = new byte[4]; + private int b4Posn; + private byte sbiCrop; + private byte sbiDecode; + private byte[] decodabet; + private int outBuffPosn; + private ByteBuf dest; - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i; - byte sbiCrop; - byte sbiDecode; - for (i = off; i < off + len; i ++) { - sbiCrop = (byte) (src.getByte(i) & 0x7f); // Only the low seven bits - sbiDecode = DECODABET[sbiCrop]; + ByteBuf decode(ByteBuf src, int off, int len, ByteBufAllocator allocator, Base64Dialect dialect) { + int len34 = (len * 3) >>> 2; + dest = allocator.buffer(len34).order(src.order()); // Upper limit on size of output + + decodabet = decodabet(dialect); + try { + src.forEachByte(off, len, this); + return dest.slice(0, outBuffPosn); + } catch (Throwable cause) { + dest.release(); + PlatformDependent.throwException(cause); + return null; + } + } + + @Override + public boolean process(byte value) throws Exception { + sbiCrop = (byte) (value & 0x7f); // Only the low seven bits + sbiDecode = decodabet[sbiCrop]; if (sbiDecode >= WHITE_SPACE_ENC) { // White space, Equals sign or better if (sbiDecode >= EQUALS_SIGN_ENC) { // Equals sign or better b4[b4Posn ++] = sbiCrop; if (b4Posn > 3) { // Quartet built - outBuffPosn += decode4to3( - b4, 0, dest, outBuffPosn, dialect); + outBuffPosn += decode4to3(b4, dest, outBuffPosn, decodabet); b4Posn = 0; // If that was the equals sign, break out of 'for' loop if (sbiCrop == EQUALS_SIGN) { - break; + return false; } } } - } else { - throw new IllegalArgumentException( - "bad Base64 input character at " + i + ": " + - src.getUnsignedByte(i) + " (decimal)"); + return true; } + throw new IllegalArgumentException( + "invalid bad Base64 input character: " + (short) (value & 0xFF) + " (decimal)"); } - return dest.slice(0, outBuffPosn); - } + private static int decode4to3(byte[] src, ByteBuf dest, int destOffset, byte[] decodabet) { + if (src[2] == EQUALS_SIGN) { + // Example: Dk== + dest.setByte(destOffset, (byte) ((decodabet[src[0]] & 0xff) << 2 | (decodabet[src[1]] & 0xff) >>> 4)); + return 1; + } - private static int decode4to3( - byte[] src, int srcOffset, - ByteBuf dest, int destOffset, Base64Dialect dialect) { + if (src[3] == EQUALS_SIGN) { + // Example: DkL= + int outBuff = (decodabet[src[0]] & 0xff) << 18 | + (decodabet[src[1]] & 0xff) << 12 | + (decodabet[src[2]] & 0xff) << 6; - byte[] DECODABET = decodabet(dialect); + // Packing bytes into a short to reduce bound and reference count checking. + if (dest.order() == ByteOrder.BIG_ENDIAN) { + dest.setShort(destOffset, (short) ((outBuff & 0xff0000) >>> 8 | (outBuff & 0xff00) >>> 8)); + } else { + dest.setShort(destOffset, (short) ((outBuff & 0xff0000) >>> 16 | (outBuff & 0xff00))); + } + return 2; + } - if (src[srcOffset + 2] == EQUALS_SIGN) { - // Example: Dk== - int outBuff = - (DECODABET[src[srcOffset ]] & 0xFF) << 18 | - (DECODABET[src[srcOffset + 1]] & 0xFF) << 12; - - dest.setByte(destOffset, (byte) (outBuff >>> 16)); - return 1; - } else if (src[srcOffset + 3] == EQUALS_SIGN) { - // Example: DkL= - int outBuff = - (DECODABET[src[srcOffset ]] & 0xFF) << 18 | - (DECODABET[src[srcOffset + 1]] & 0xFF) << 12 | - (DECODABET[src[srcOffset + 2]] & 0xFF) << 6; - - dest.setByte(destOffset , (byte) (outBuff >>> 16)); - dest.setByte(destOffset + 1, (byte) (outBuff >>> 8)); - return 2; - } else { // Example: DkLE - int outBuff; + final int outBuff; try { - outBuff = - (DECODABET[src[srcOffset ]] & 0xFF) << 18 | - (DECODABET[src[srcOffset + 1]] & 0xFF) << 12 | - (DECODABET[src[srcOffset + 2]] & 0xFF) << 6 | - DECODABET[src[srcOffset + 3]] & 0xFF; + outBuff = (decodabet[src[0]] & 0xff) << 18 | + (decodabet[src[1]] & 0xff) << 12 | + (decodabet[src[2]] & 0xff) << 6 | + decodabet[src[3]] & 0xff; } catch (IndexOutOfBoundsException ignored) { throw new IllegalArgumentException("not encoded in Base64"); } - - dest.setByte(destOffset , (byte) (outBuff >> 16)); - dest.setByte(destOffset + 1, (byte) (outBuff >> 8)); - dest.setByte(destOffset + 2, (byte) outBuff); + // Just directly set it as medium + if (dest.order() == ByteOrder.BIG_ENDIAN) { + dest.setMedium(destOffset, outBuff); + } else { + dest.setMedium(destOffset, ByteBufUtil.swapMedium(outBuff)); + } return 3; } } diff --git a/codec/src/test/java/io/netty/handler/codec/base64/Base64Test.java b/codec/src/test/java/io/netty/handler/codec/base64/Base64Test.java index 6b8622ac24..4be23a8d86 100644 --- a/codec/src/test/java/io/netty/handler/codec/base64/Base64Test.java +++ b/codec/src/test/java/io/netty/handler/codec/base64/Base64Test.java @@ -18,10 +18,12 @@ package io.netty.handler.codec.base64; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; +import io.netty.util.internal.PlatformDependent; import org.junit.Test; import java.io.ByteArrayInputStream; +import java.nio.ByteOrder; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -116,4 +118,42 @@ public class Base64Test { encoded.release(); } } + + @Test + public void testEncodeDecodeBE() { + testEncodeDecode(ByteOrder.BIG_ENDIAN); + } + + @Test + public void testEncodeDecodeLE() { + testEncodeDecode(ByteOrder.LITTLE_ENDIAN); + } + + private static void testEncodeDecode(ByteOrder order) { + testEncodeDecode(64, order); + testEncodeDecode(128, order); + testEncodeDecode(512, order); + testEncodeDecode(1024, order); + testEncodeDecode(4096, order); + testEncodeDecode(8192, order); + testEncodeDecode(16384, order); + } + + private static void testEncodeDecode(int size, ByteOrder order) { + byte[] bytes = new byte[size]; + PlatformDependent.threadLocalRandom().nextBytes(bytes); + + ByteBuf src = Unpooled.wrappedBuffer(bytes).order(order); + ByteBuf encoded = Base64.encode(src); + ByteBuf decoded = Base64.decode(encoded); + ByteBuf expectedBuf = Unpooled.wrappedBuffer(bytes); + try { + assertEquals(expectedBuf, decoded); + } finally { + src.release(); + encoded.release(); + decoded.release(); + expectedBuf.release(); + } + } }