Improve performance of Base64.decode and encode methods.
Motivation: The decode and encode method uses getByte(...) and setByte(...) in loops which can be very expensive because of bounds / reference-count checking. Beside this it also slows-down a lot when paranoid leak-detection is enabled as it will track each access. Modifications: - Pack bytes into int / short and so reduce operations on the ByteBuf - Use ByteProcessor to reduce getByte calls. Result: Better performance in general. Also when you run the build with -Pleak the handler module will build in 1/4 of the time it took before.
This commit is contained in:
parent
0623c6c533
commit
7feb92959e
@ -21,6 +21,11 @@ package io.netty.handler.codec.base64;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
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
|
* Utility class for {@link ByteBuf} that encodes and decodes to and from
|
||||||
@ -109,7 +114,6 @@ public final class Base64 {
|
|||||||
|
|
||||||
public static ByteBuf encode(
|
public static ByteBuf encode(
|
||||||
ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect, ByteBufAllocator allocator) {
|
ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect, ByteBufAllocator allocator) {
|
||||||
|
|
||||||
if (src == null) {
|
if (src == null) {
|
||||||
throw new NullPointerException("src");
|
throw new NullPointerException("src");
|
||||||
}
|
}
|
||||||
@ -117,17 +121,18 @@ public final class Base64 {
|
|||||||
throw new NullPointerException("dialect");
|
throw new NullPointerException("dialect");
|
||||||
}
|
}
|
||||||
|
|
||||||
int len43 = len * 4 / 3;
|
int len43 = (len << 2) / 3;
|
||||||
ByteBuf dest = allocator.buffer(
|
ByteBuf dest = allocator.buffer(
|
||||||
len43 +
|
len43 +
|
||||||
(len % 3 > 0 ? 4 : 0) + // Account for padding
|
(len % 3 > 0 ? 4 : 0) + // Account for padding
|
||||||
(breakLines ? len43 / MAX_LINE_LENGTH : 0)).order(src.order()); // New lines
|
(breakLines ? len43 / MAX_LINE_LENGTH : 0)).order(src.order()); // New lines
|
||||||
|
byte[] alphabet = alphabet(dialect);
|
||||||
int d = 0;
|
int d = 0;
|
||||||
int e = 0;
|
int e = 0;
|
||||||
int len2 = len - 2;
|
int len2 = len - 2;
|
||||||
int lineLength = 0;
|
int lineLength = 0;
|
||||||
for (; d < len2; d += 3, e += 4) {
|
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;
|
lineLength += 4;
|
||||||
|
|
||||||
@ -139,7 +144,7 @@ public final class Base64 {
|
|||||||
} // end for: each piece of array
|
} // end for: each piece of array
|
||||||
|
|
||||||
if (d < len) {
|
if (d < len) {
|
||||||
encode3to4(src, d + off, len - d, dest, e, dialect);
|
encode3to4(src, d + off, len - d, dest, e, alphabet);
|
||||||
e += 4;
|
e += 4;
|
||||||
} // end if: some padding needed
|
} // end if: some padding needed
|
||||||
|
|
||||||
@ -152,11 +157,7 @@ public final class Base64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void encode3to4(
|
private static void encode3to4(
|
||||||
ByteBuf src, int srcOffset, int numSigBytes,
|
ByteBuf src, int srcOffset, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
|
||||||
ByteBuf dest, int destOffset, Base64Dialect dialect) {
|
|
||||||
|
|
||||||
byte[] ALPHABET = alphabet(dialect);
|
|
||||||
|
|
||||||
// 1 2 3
|
// 1 2 3
|
||||||
// 01234567890123456789012345678901 Bit position
|
// 01234567890123456789012345678901 Bit position
|
||||||
// --------000000001111111122222222 Array position from threeBytes
|
// --------000000001111111122222222 Array position from threeBytes
|
||||||
@ -168,30 +169,110 @@ public final class Base64 {
|
|||||||
// significant bytes passed in the array.
|
// significant bytes passed in the array.
|
||||||
// We have to shift left 24 in order to flush out the 1's that appear
|
// 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.
|
// when Java treats a value as negative that is cast from a byte to an int.
|
||||||
int inBuff =
|
if (src.order() == ByteOrder.BIG_ENDIAN) {
|
||||||
(numSigBytes > 0? src.getByte(srcOffset) << 24 >>> 8 : 0) |
|
final int inBuff;
|
||||||
(numSigBytes > 1? src.getByte(srcOffset + 1) << 24 >>> 16 : 0) |
|
switch (numSigBytes) {
|
||||||
(numSigBytes > 2? src.getByte(srcOffset + 2) << 24 >>> 24 : 0);
|
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) {
|
switch (numSigBytes) {
|
||||||
case 3:
|
case 3:
|
||||||
dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]);
|
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
|
||||||
dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
|
alphabet[inBuff >>> 12 & 0x3f] << 16 |
|
||||||
dest.setByte(destOffset + 2, ALPHABET[inBuff >>> 6 & 0x3f]);
|
alphabet[inBuff >>> 6 & 0x3f] << 8 |
|
||||||
dest.setByte(destOffset + 3, ALPHABET[inBuff & 0x3f]);
|
alphabet[inBuff & 0x3f]);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]);
|
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
|
||||||
dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
|
alphabet[inBuff >>> 12 & 0x3f] << 16 |
|
||||||
dest.setByte(destOffset + 2, ALPHABET[inBuff >>> 6 & 0x3f]);
|
alphabet[inBuff >>> 6 & 0x3f] << 8 |
|
||||||
dest.setByte(destOffset + 3, EQUALS_SIGN);
|
EQUALS_SIGN);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]);
|
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
|
||||||
dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
|
alphabet[inBuff >>> 12 & 0x3f] << 16 |
|
||||||
dest.setByte(destOffset + 2, EQUALS_SIGN);
|
EQUALS_SIGN << 8 |
|
||||||
dest.setByte(destOffset + 3, EQUALS_SIGN);
|
EQUALS_SIGN);
|
||||||
break;
|
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) {
|
public static ByteBuf decode(ByteBuf src, Base64Dialect dialect) {
|
||||||
|
|
||||||
if (src == null) {
|
if (src == null) {
|
||||||
throw new NullPointerException("src");
|
throw new NullPointerException("src");
|
||||||
}
|
}
|
||||||
@ -222,7 +302,6 @@ public final class Base64 {
|
|||||||
|
|
||||||
public static ByteBuf decode(
|
public static ByteBuf decode(
|
||||||
ByteBuf src, int off, int len, Base64Dialect dialect, ByteBufAllocator allocator) {
|
ByteBuf src, int off, int len, Base64Dialect dialect, ByteBufAllocator allocator) {
|
||||||
|
|
||||||
if (src == null) {
|
if (src == null) {
|
||||||
throw new NullPointerException("src");
|
throw new NullPointerException("src");
|
||||||
}
|
}
|
||||||
@ -230,85 +309,96 @@ public final class Base64 {
|
|||||||
throw new NullPointerException("dialect");
|
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;
|
private static final class Decoder implements ByteProcessor {
|
||||||
ByteBuf dest = allocator.buffer(len34).order(src.order()); // Upper limit on size of output
|
private final byte[] b4 = new byte[4];
|
||||||
int outBuffPosn = 0;
|
private int b4Posn;
|
||||||
|
private byte sbiCrop;
|
||||||
|
private byte sbiDecode;
|
||||||
|
private byte[] decodabet;
|
||||||
|
private int outBuffPosn;
|
||||||
|
private ByteBuf dest;
|
||||||
|
|
||||||
byte[] b4 = new byte[4];
|
ByteBuf decode(ByteBuf src, int off, int len, ByteBufAllocator allocator, Base64Dialect dialect) {
|
||||||
int b4Posn = 0;
|
int len34 = (len * 3) >>> 2;
|
||||||
int i;
|
dest = allocator.buffer(len34).order(src.order()); // Upper limit on size of output
|
||||||
byte sbiCrop;
|
|
||||||
byte sbiDecode;
|
decodabet = decodabet(dialect);
|
||||||
for (i = off; i < off + len; i ++) {
|
try {
|
||||||
sbiCrop = (byte) (src.getByte(i) & 0x7f); // Only the low seven bits
|
src.forEachByte(off, len, this);
|
||||||
sbiDecode = DECODABET[sbiCrop];
|
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 >= WHITE_SPACE_ENC) { // White space, Equals sign or better
|
||||||
if (sbiDecode >= EQUALS_SIGN_ENC) { // Equals sign or better
|
if (sbiDecode >= EQUALS_SIGN_ENC) { // Equals sign or better
|
||||||
b4[b4Posn ++] = sbiCrop;
|
b4[b4Posn ++] = sbiCrop;
|
||||||
if (b4Posn > 3) { // Quartet built
|
if (b4Posn > 3) { // Quartet built
|
||||||
outBuffPosn += decode4to3(
|
outBuffPosn += decode4to3(b4, dest, outBuffPosn, decodabet);
|
||||||
b4, 0, dest, outBuffPosn, dialect);
|
|
||||||
b4Posn = 0;
|
b4Posn = 0;
|
||||||
|
|
||||||
// If that was the equals sign, break out of 'for' loop
|
// If that was the equals sign, break out of 'for' loop
|
||||||
if (sbiCrop == EQUALS_SIGN) {
|
if (sbiCrop == EQUALS_SIGN) {
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return true;
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"bad Base64 input character at " + i + ": " +
|
|
||||||
src.getUnsignedByte(i) + " (decimal)");
|
|
||||||
}
|
}
|
||||||
|
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(
|
if (src[3] == EQUALS_SIGN) {
|
||||||
byte[] src, int srcOffset,
|
// Example: DkL=
|
||||||
ByteBuf dest, int destOffset, Base64Dialect dialect) {
|
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
|
// Example: DkLE
|
||||||
int outBuff;
|
final int outBuff;
|
||||||
try {
|
try {
|
||||||
outBuff =
|
outBuff = (decodabet[src[0]] & 0xff) << 18 |
|
||||||
(DECODABET[src[srcOffset ]] & 0xFF) << 18 |
|
(decodabet[src[1]] & 0xff) << 12 |
|
||||||
(DECODABET[src[srcOffset + 1]] & 0xFF) << 12 |
|
(decodabet[src[2]] & 0xff) << 6 |
|
||||||
(DECODABET[src[srcOffset + 2]] & 0xFF) << 6 |
|
decodabet[src[3]] & 0xff;
|
||||||
DECODABET[src[srcOffset + 3]] & 0xFF;
|
|
||||||
} catch (IndexOutOfBoundsException ignored) {
|
} catch (IndexOutOfBoundsException ignored) {
|
||||||
throw new IllegalArgumentException("not encoded in Base64");
|
throw new IllegalArgumentException("not encoded in Base64");
|
||||||
}
|
}
|
||||||
|
// Just directly set it as medium
|
||||||
dest.setByte(destOffset , (byte) (outBuff >> 16));
|
if (dest.order() == ByteOrder.BIG_ENDIAN) {
|
||||||
dest.setByte(destOffset + 1, (byte) (outBuff >> 8));
|
dest.setMedium(destOffset, outBuff);
|
||||||
dest.setByte(destOffset + 2, (byte) outBuff);
|
} else {
|
||||||
|
dest.setMedium(destOffset, ByteBufUtil.swapMedium(outBuff));
|
||||||
|
}
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,12 @@ package io.netty.handler.codec.base64;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
@ -116,4 +118,42 @@ public class Base64Test {
|
|||||||
encoded.release();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user