From ea725b38816179d1954d1d7448338a03ae5d6d9e Mon Sep 17 00:00:00 2001 From: Graham Date: Fri, 21 May 2021 19:17:22 +0100 Subject: [PATCH] Add extension methods for reading/writing null-terminated Cp1252 strings Signed-off-by: Graham --- buffer/build.gradle.kts | 1 + .../org/openrs2/buffer/ByteBufExtensions.kt | 37 ++++++ .../openrs2/buffer/ByteBufExtensionsTest.kt | 122 ++++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/buffer/build.gradle.kts b/buffer/build.gradle.kts index 373b841f..d8b2eae1 100644 --- a/buffer/build.gradle.kts +++ b/buffer/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { api(libs.guice) api(libs.netty.buffer) + implementation(projects.util) implementation(libs.guava) } diff --git a/buffer/src/main/kotlin/org/openrs2/buffer/ByteBufExtensions.kt b/buffer/src/main/kotlin/org/openrs2/buffer/ByteBufExtensions.kt index 42de507f..de08587c 100644 --- a/buffer/src/main/kotlin/org/openrs2/buffer/ByteBufExtensions.kt +++ b/buffer/src/main/kotlin/org/openrs2/buffer/ByteBufExtensions.kt @@ -4,9 +4,13 @@ import com.google.common.base.Preconditions import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBufUtil import io.netty.buffer.Unpooled +import io.netty.util.ByteProcessor +import org.openrs2.util.charset.Cp1252Charset import java.nio.charset.Charset import java.util.zip.CRC32 +private const val STRING_VERSION = 0 + public fun wrappedBuffer(vararg bytes: Byte): ByteBuf { return Unpooled.wrappedBuffer(bytes) } @@ -91,6 +95,39 @@ public fun ByteBuf.writeUnsignedIntSmart(v: Int): ByteBuf { return this } +public fun ByteBuf.readString(): String { + val start = readerIndex() + + val end = forEachByte(ByteProcessor.FIND_NUL) + require(end != -1) { + "Unterminated string" + } + + val s = toString(start, end - start, Cp1252Charset) + readerIndex(end + 1) + return s +} + +public fun ByteBuf.writeString(s: CharSequence): ByteBuf { + writeCharSequence(s, Cp1252Charset) + writeByte(0) + return this +} + +public fun ByteBuf.readVersionedString(): String { + val version = readUnsignedByte().toInt() + require(version == STRING_VERSION) { + "Unsupported version number $version" + } + return readString() +} + +public fun ByteBuf.writeVersionedString(s: CharSequence): ByteBuf { + writeByte(STRING_VERSION) + writeString(s) + return this +} + public fun ByteBuf.crc32(): Int { return crc32(readerIndex(), readableBytes()) } diff --git a/buffer/src/test/kotlin/org/openrs2/buffer/ByteBufExtensionsTest.kt b/buffer/src/test/kotlin/org/openrs2/buffer/ByteBufExtensionsTest.kt index 050ad750..e3a5e25d 100644 --- a/buffer/src/test/kotlin/org/openrs2/buffer/ByteBufExtensionsTest.kt +++ b/buffer/src/test/kotlin/org/openrs2/buffer/ByteBufExtensionsTest.kt @@ -407,6 +407,128 @@ class ByteBufExtensionsTest { } } + @Test + fun testReadString() { + wrappedBuffer(0).use { buf -> + assertEquals("", buf.readString()) + assertFalse(buf.isReadable) + } + + wrappedBuffer( + 214.toByte(), + 'p'.code.toByte(), + 'e'.code.toByte(), + 'n'.code.toByte(), + 'R'.code.toByte(), + 'S'.code.toByte(), + '2'.code.toByte(), + 0 + ).use { buf -> + assertEquals("ÖpenRS2", buf.readString()) + assertFalse(buf.isReadable) + } + + assertFailsWith { + Unpooled.EMPTY_BUFFER.readString() + } + } + + @Test + fun testWriteString() { + ByteBufAllocator.DEFAULT.buffer().use { actual -> + actual.writeString("") + + wrappedBuffer(0).use { expected -> + assertEquals(expected, actual) + } + } + + ByteBufAllocator.DEFAULT.buffer().use { actual -> + actual.writeString("ÖpenRS2") + + wrappedBuffer( + 214.toByte(), + 'p'.code.toByte(), + 'e'.code.toByte(), + 'n'.code.toByte(), + 'R'.code.toByte(), + 'S'.code.toByte(), + '2'.code.toByte(), + 0 + ).use { expected -> + assertEquals(expected, actual) + } + } + } + + @Test + fun testReadVersionedString() { + wrappedBuffer(0, 0).use { buf -> + assertEquals("", buf.readVersionedString()) + assertFalse(buf.isReadable) + } + + wrappedBuffer( + 0, + 214.toByte(), + 'p'.code.toByte(), + 'e'.code.toByte(), + 'n'.code.toByte(), + 'R'.code.toByte(), + 'S'.code.toByte(), + '2'.code.toByte(), + 0 + ).use { buf -> + assertEquals("ÖpenRS2", buf.readVersionedString()) + assertFalse(buf.isReadable) + } + + assertFailsWith { + Unpooled.EMPTY_BUFFER.readVersionedString() + } + + wrappedBuffer(0).use { buf -> + assertFailsWith { + buf.readVersionedString() + } + } + + wrappedBuffer(1, 0).use { buf -> + assertFailsWith { + buf.readVersionedString() + } + } + } + + @Test + fun testWriteVersionedString() { + ByteBufAllocator.DEFAULT.buffer().use { actual -> + actual.writeVersionedString("") + + wrappedBuffer(0, 0).use { expected -> + assertEquals(expected, actual) + } + } + + ByteBufAllocator.DEFAULT.buffer().use { actual -> + actual.writeVersionedString("ÖpenRS2") + + wrappedBuffer( + 0, + 214.toByte(), + 'p'.code.toByte(), + 'e'.code.toByte(), + 'n'.code.toByte(), + 'R'.code.toByte(), + 'S'.code.toByte(), + '2'.code.toByte(), + 0 + ).use { expected -> + assertEquals(expected, actual) + } + } + } + @Test fun testCrc32() { val s = "AAThe quick brown fox jumps over the lazy dogA"