mirror of
https://github.com/GTNewHorizons/CodeChickenCore.git
synced 2026-11-14 01:04:28 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71d332d43d |
No files matched your search
@@ -2,7 +2,6 @@ package codechicken.lib.packet;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
@@ -51,8 +50,6 @@ import io.netty.util.AttributeKey;
|
||||
|
||||
public final class PacketCustom implements MCDataInput, MCDataOutput {
|
||||
|
||||
private static final int MAX_DECOMPRESSED_SIZE = 2 * 1024 * 1024;
|
||||
|
||||
public static interface ICustomPacketHandler {
|
||||
}
|
||||
|
||||
@@ -193,7 +190,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
|
||||
byteBuf = payload;
|
||||
|
||||
type = byteBuf.readUnsignedByte();
|
||||
if (type > 0x80) decompressPayload();
|
||||
if (type > 0x80) decompress();
|
||||
type &= 0x7F;
|
||||
}
|
||||
|
||||
@@ -207,61 +204,44 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
|
||||
byteBuf.writeByte(type);
|
||||
}
|
||||
|
||||
private void decompressPayload() {
|
||||
if (byteBuf.readableBytes() < 4) throw new IllegalArgumentException("Missing compressed packet length");
|
||||
int declaredLength = byteBuf.readInt();
|
||||
if (declaredLength < 0 || declaredLength > MAX_DECOMPRESSED_SIZE)
|
||||
throw new IllegalArgumentException("Invalid decompressed packet length: " + declaredLength);
|
||||
|
||||
/**
|
||||
* Decompresses the remaining ByteBuf (after type has been read) using Snappy
|
||||
*/
|
||||
private void decompress() {
|
||||
Inflater inflater = new Inflater();
|
||||
ByteBuf decodedPayload = null;
|
||||
try {
|
||||
byte[] compressedInput = new byte[byteBuf.readableBytes()];
|
||||
byteBuf.readBytes(compressedInput);
|
||||
inflater.setInput(compressedInput);
|
||||
decodedPayload = Unpooled.buffer(Math.min(declaredLength, 8192), declaredLength);
|
||||
byte[] inflatedChunk = new byte[Math.min(declaredLength + 1, 8192)];
|
||||
while (!inflater.finished()) {
|
||||
int inflatedBytes = inflater.inflate(inflatedChunk);
|
||||
if (inflatedBytes > declaredLength - decodedPayload.writerIndex())
|
||||
throw new IllegalArgumentException("Decompressed packet exceeds declared length");
|
||||
if (inflatedBytes == 0 && !inflater.finished())
|
||||
throw new IllegalArgumentException("Incomplete or dictionary-dependent compressed packet");
|
||||
decodedPayload.writeBytes(inflatedChunk, 0, inflatedBytes);
|
||||
}
|
||||
if (decodedPayload.writerIndex() != declaredLength || inflater.getRemaining() != 0)
|
||||
throw new IllegalArgumentException(
|
||||
"Compressed packet does not match declared length or has trailing data");
|
||||
byteBuf = decodedPayload;
|
||||
decodedPayload = null;
|
||||
} catch (DataFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid compressed packet", e);
|
||||
int len = byteBuf.readInt();
|
||||
ByteBuf out = Unpooled.buffer(len);
|
||||
inflater.setInput(byteBuf.array(), byteBuf.readerIndex(), byteBuf.readableBytes());
|
||||
inflater.inflate(out.array());
|
||||
out.writerIndex(len);
|
||||
byteBuf = out;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (decodedPayload != null) decodedPayload.release();
|
||||
inflater.end();
|
||||
}
|
||||
}
|
||||
|
||||
private void compressPayload() {
|
||||
if ((byteBuf.getUnsignedByte(0) & 0x80) != 0) return;
|
||||
|
||||
int payloadLength = byteBuf.writerIndex() - 1;
|
||||
if (payloadLength > MAX_DECOMPRESSED_SIZE)
|
||||
throw new IllegalArgumentException("Packet payload exceeds 2 MiB decompressed limit: " + payloadLength);
|
||||
|
||||
/**
|
||||
* Compresses the payload ByteBuf after the type byte
|
||||
*/
|
||||
private void do_compress() {
|
||||
Deflater deflater = new Deflater();
|
||||
try {
|
||||
byteBuf.readerIndex(1);
|
||||
deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), payloadLength);
|
||||
int len = byteBuf.readableBytes();
|
||||
deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), len);
|
||||
deflater.finish();
|
||||
ByteBuf compressedPayload = Unpooled.buffer(payloadLength + 5);
|
||||
int compressedLength = deflater.deflate(compressedPayload.array(), 5, payloadLength);
|
||||
if (compressedLength >= payloadLength - 5 || !deflater.finished()) return;
|
||||
ByteBuf out = Unpooled.buffer(len + 5);
|
||||
int clen = deflater.deflate(out.array(), 5, len);
|
||||
if (clen >= len - 5 || !deflater.finished()) // not worth compressing, gets larger
|
||||
return;
|
||||
|
||||
compressedPayload.setByte(0, type | 0x80);
|
||||
compressedPayload.setInt(1, payloadLength);
|
||||
compressedPayload.writerIndex(compressedLength + 5);
|
||||
byteBuf = compressedPayload;
|
||||
out.setByte(0, type | 0x80);
|
||||
out.setInt(1, len);
|
||||
out.writerIndex(clen + 5);
|
||||
byteBuf = out;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
@@ -494,7 +474,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
|
||||
public FMLProxyPacket toPacket() {
|
||||
if (incoming()) throw new IllegalStateException("Tried to write an incoming packet");
|
||||
|
||||
if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) compressPayload();
|
||||
if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) do_compress();
|
||||
|
||||
// FML packet impl returns the whole of the backing array, copy used portion of array to another ByteBuf
|
||||
return new FMLProxyPacket(byteBuf.copy(), channel);
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
package codechicken.lib.packet;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import cpw.mods.fml.common.network.internal.FMLProxyPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public class PacketCustomTest {
|
||||
|
||||
private static final int MAX_DECOMPRESSED_SIZE = 2 * 1024 * 1024;
|
||||
|
||||
@Test
|
||||
public void rejectsLargeDeclarationWithoutCompressedData() {
|
||||
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(8 * 1024 * 1024));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { -1, Integer.MIN_VALUE, MAX_DECOMPRESSED_SIZE + 1, Integer.MAX_VALUE })
|
||||
public void rejectsInvalidDeclaredLengths(int length) {
|
||||
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(length));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMissingCompressedDataWithinLimit() {
|
||||
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(MAX_DECOMPRESSED_SIZE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsCompleteStreamAboveDecodeLimit() {
|
||||
assertRejected(compressed(new byte[MAX_DECOMPRESSED_SIZE + 1]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsTruncatedLengthHeader() {
|
||||
assertRejected(Unpooled.buffer(4).writeByte(0x81).writeMedium(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMismatchedInflatedLengths() {
|
||||
byte[] data = new byte[16384];
|
||||
assertRejected(compressed(data).setInt(1, data.length - 1));
|
||||
assertRejected(compressed(data).setInt(1, data.length + 1));
|
||||
assertRejected(compressed(data).setInt(1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsTruncatedStreamEvenWhenAllOutputWasProduced() {
|
||||
ByteBuf payload = compressed(new byte[16384]);
|
||||
payload.writerIndex(payload.writerIndex() - 1);
|
||||
assertRejected(payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsCorruptStream() {
|
||||
ByteBuf payload = compressed(new byte[16384]);
|
||||
payload.setByte(payload.writerIndex() - 1, payload.getByte(payload.writerIndex() - 1) ^ 1);
|
||||
assertRejected(payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsDictionaryDependentStream() {
|
||||
assertRejected(compressed(new byte[16384], new byte[] { 1, 2, 3 }));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsTrailingCompressedData() {
|
||||
assertRejected(compressed(new byte[16384]).writeByte(0));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 1, 8192, 8193, MAX_DECOMPRESSED_SIZE })
|
||||
public void decodesCompleteStreams(int length) {
|
||||
byte[] data = new byte[length];
|
||||
Arrays.fill(data, (byte) 42);
|
||||
assertDecoded(compressed(data), data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodesSlicedAndDirectBuffers() {
|
||||
byte[] data = new byte[16384];
|
||||
Arrays.fill(data, (byte) 42);
|
||||
ByteBuf payload = compressed(data);
|
||||
ByteBuf prefixed = Unpooled.buffer().writeLong(-1).writeBytes(payload, 0, payload.readableBytes());
|
||||
ByteBuf direct = Unpooled.directBuffer().writeBytes(payload);
|
||||
payload.release();
|
||||
try {
|
||||
assertDecoded(prefixed.slice(8, prefixed.readableBytes() - 8).retain(), data);
|
||||
assertDecoded(direct, data);
|
||||
} finally {
|
||||
prefixed.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preservesUncompressedPackets() {
|
||||
ByteBuf payload = Unpooled.buffer().writeByte(7).writeInt(42);
|
||||
try {
|
||||
PacketCustom packet = new PacketCustom(payload);
|
||||
assertEquals(7, packet.getType());
|
||||
assertEquals(42, packet.readInt());
|
||||
assertEquals(0, packet.getByteBuf().readableBytes());
|
||||
} finally {
|
||||
payload.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundTripsOutgoingCompression() {
|
||||
for (int length : new int[] { 16, 64, 40000, MAX_DECOMPRESSED_SIZE }) {
|
||||
byte[] data = new byte[length];
|
||||
Arrays.fill(data, (byte) 42);
|
||||
PacketCustom outgoing = new PacketCustom("PacketTest", 7);
|
||||
FMLProxyPacket wire = null;
|
||||
try {
|
||||
outgoing.writeByteArray(data);
|
||||
if (length <= 64) outgoing.compress();
|
||||
wire = outgoing.toPacket();
|
||||
assertEquals(length == 16 ? 7 : 0x87, wire.payload().getUnsignedByte(0));
|
||||
assertDecoded(wire.payload().retain(), data);
|
||||
} finally {
|
||||
if (wire != null) wire.payload().release();
|
||||
outgoing.getByteBuf().release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { false, true })
|
||||
public void repeatedSerializationPreservesCompressedPayload(boolean forceCompression) {
|
||||
byte[] data = new byte[40000];
|
||||
Arrays.fill(data, (byte) 42);
|
||||
PacketCustom outgoing = new PacketCustom("PacketTest", 7).writeByteArray(data);
|
||||
if (forceCompression) outgoing.compress();
|
||||
try {
|
||||
for (int serialization = 0; serialization < 3; serialization++) {
|
||||
FMLProxyPacket wire = outgoing.toPacket();
|
||||
try {
|
||||
assertEquals(0x87, wire.payload().getUnsignedByte(0));
|
||||
assertDecoded(wire.payload().retain(), data);
|
||||
} finally {
|
||||
wire.payload().release();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
outgoing.getByteBuf().release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsOutgoingPayloadAboveDecodeLimit() {
|
||||
PacketCustom outgoing = new PacketCustom("PacketTest", 7);
|
||||
try {
|
||||
outgoing.getByteBuf().writeZero(MAX_DECOMPRESSED_SIZE + 1);
|
||||
assertThrows(IllegalArgumentException.class, outgoing::toPacket);
|
||||
} finally {
|
||||
outgoing.getByteBuf().release();
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuf compressed(byte[] data) {
|
||||
return compressed(data, null);
|
||||
}
|
||||
|
||||
private static ByteBuf compressed(byte[] data, byte[] dictionary) {
|
||||
Deflater deflater = new Deflater();
|
||||
try {
|
||||
if (dictionary != null) deflater.setDictionary(dictionary);
|
||||
deflater.setInput(data);
|
||||
deflater.finish();
|
||||
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
|
||||
byte[] scratch = new byte[8192];
|
||||
while (!deflater.finished()) {
|
||||
int count = deflater.deflate(scratch);
|
||||
compressed.write(scratch, 0, count);
|
||||
}
|
||||
return Unpooled.buffer().writeByte(0x87).writeInt(data.length).writeBytes(compressed.toByteArray());
|
||||
} finally {
|
||||
deflater.end();
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertDecoded(ByteBuf payload, byte[] expected) {
|
||||
PacketCustom packet = null;
|
||||
try {
|
||||
packet = new PacketCustom(payload);
|
||||
assertEquals(7, packet.getType());
|
||||
assertEquals(expected.length, packet.getByteBuf().readableBytes());
|
||||
byte[] actual = new byte[expected.length];
|
||||
packet.getByteBuf().readBytes(actual);
|
||||
assertArrayEquals(expected, actual);
|
||||
} finally {
|
||||
if (packet != null && packet.getByteBuf() != payload) packet.getByteBuf().release();
|
||||
payload.release();
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertRejected(ByteBuf payload) {
|
||||
try {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
PacketCustom packet = new PacketCustom(payload);
|
||||
if (packet.getByteBuf() != payload) packet.getByteBuf().release();
|
||||
});
|
||||
} finally {
|
||||
payload.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user