diff --git a/buffer/src/main/java/dev/openrs2/buffer/ByteBufExtensions.kt b/buffer/src/main/java/dev/openrs2/buffer/ByteBufExtensions.kt index ea88e3b3..bbc531d4 100644 --- a/buffer/src/main/java/dev/openrs2/buffer/ByteBufExtensions.kt +++ b/buffer/src/main/java/dev/openrs2/buffer/ByteBufExtensions.kt @@ -1,6 +1,8 @@ package dev.openrs2.buffer import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufUtil +import java.util.zip.CRC32 public fun ByteBuf.readShortSmart(): Int { val peek = getUnsignedByte(readerIndex()).toInt() @@ -77,3 +79,29 @@ public fun ByteBuf.writeUnsignedIntSmart(v: Int): ByteBuf { return this } + +public fun ByteBuf.crc32(): Int { + return crc32(readerIndex(), readableBytes()) +} + +public fun ByteBuf.crc32(index: Int, len: Int): Int { + if (index < 0 || index >= capacity() || len < 0 || (index + len) >= capacity()) { + throw IndexOutOfBoundsException() + } + + val crc = CRC32() + val count = nioBufferCount() + + when { + hasArray() -> crc.update(array(), arrayOffset() + index, len) + count > 1 -> { + for (b in nioBuffers(index, len)) { + crc.update(b) + } + } + count == 1 -> crc.update(nioBuffer(index, len)) + else -> crc.update(ByteBufUtil.getBytes(this, index, len)) + } + + return crc.value.toInt() +} diff --git a/buffer/src/test/java/dev/openrs2/buffer/ByteBufExtensionsTest.kt b/buffer/src/test/java/dev/openrs2/buffer/ByteBufExtensionsTest.kt index f2d55591..76f3a18e 100644 --- a/buffer/src/test/java/dev/openrs2/buffer/ByteBufExtensionsTest.kt +++ b/buffer/src/test/java/dev/openrs2/buffer/ByteBufExtensionsTest.kt @@ -391,4 +391,64 @@ object ByteBufExtensionsTest { } } } + + @Test + fun testCrc32() { + val s = "AAThe quick brown fox jumps over the lazy dogA" + + /* + * Tests the hasArray() case. The slicedBuf trickery is to allow us + * to test a non-zero arrayOffset(). + */ + Unpooled.wrappedBuffer(s.toByteArray()).use { buf -> + val slicedBuf = buf.slice(1, buf.readableBytes() - 1) + assertEquals(0x414FA339, slicedBuf.crc32(1, slicedBuf.writerIndex() - 2)) + } + + // Tests the nioBufferCount() == 1 case. + Unpooled.wrappedBuffer(s.toByteArray()).use { buf -> + ByteBufAllocator.DEFAULT.directBuffer().use { directBuf -> + directBuf.writeBytes(buf, 0, buf.capacity()) + + assertEquals(0x414FA339, directBuf.crc32(2, directBuf.writerIndex() - 3)) + } + } + + // Tests the nioBufferCount() > 1 case. + Unpooled.wrappedBuffer( + Unpooled.wrappedBuffer("AAThe quick brown fox ".toByteArray()), + Unpooled.wrappedBuffer("jumps over the lazy dogA".toByteArray()) + ).use { buf -> + assertEquals(0x414FA339, buf.crc32(2, buf.writerIndex() - 3)) + } + + /* + * Check the crc32() method (with no arguments) sets the index/length + * correctly. + */ + Unpooled.wrappedBuffer(s.toByteArray()).use { buf -> + buf.readerIndex(2) + buf.writerIndex(buf.writerIndex() - 1) + assertEquals(0x414FA339, buf.crc32()) + } + } + + @Test + fun testCrc32Bounds() { + assertThrows { + Unpooled.EMPTY_BUFFER.crc32(-1, 0) + } + + assertThrows { + Unpooled.EMPTY_BUFFER.crc32(0, -1) + } + + assertThrows { + Unpooled.EMPTY_BUFFER.crc32(1, 0) + } + + assertThrows { + Unpooled.EMPTY_BUFFER.crc32(0, 1) + } + } }