diff --git a/protocol/build.gradle.kts b/protocol/build.gradle.kts index 33835985..de94f3bf 100644 --- a/protocol/build.gradle.kts +++ b/protocol/build.gradle.kts @@ -4,6 +4,7 @@ plugins { } dependencies { + api(project(":crypto")) api("io.netty:netty-codec:${Versions.netty}") implementation(project(":buffer")) diff --git a/protocol/src/main/kotlin/org/openrs2/protocol/EmptyPacketCodec.kt b/protocol/src/main/kotlin/org/openrs2/protocol/EmptyPacketCodec.kt new file mode 100644 index 00000000..e6a8f8fc --- /dev/null +++ b/protocol/src/main/kotlin/org/openrs2/protocol/EmptyPacketCodec.kt @@ -0,0 +1,16 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf + +public abstract class EmptyPacketCodec( + private val packet: T, + opcode: Int +) : PacketCodec(packet.javaClass, opcode, length = 0) { + override fun decode(input: ByteBuf): T { + return packet + } + + override fun encode(input: T, output: ByteBuf) { + // empty + } +} diff --git a/protocol/src/main/kotlin/org/openrs2/protocol/Packet.kt b/protocol/src/main/kotlin/org/openrs2/protocol/Packet.kt new file mode 100644 index 00000000..d93c13f9 --- /dev/null +++ b/protocol/src/main/kotlin/org/openrs2/protocol/Packet.kt @@ -0,0 +1,3 @@ +package org.openrs2.protocol + +public interface Packet diff --git a/protocol/src/main/kotlin/org/openrs2/protocol/PacketCodec.kt b/protocol/src/main/kotlin/org/openrs2/protocol/PacketCodec.kt new file mode 100644 index 00000000..5d92818b --- /dev/null +++ b/protocol/src/main/kotlin/org/openrs2/protocol/PacketCodec.kt @@ -0,0 +1,47 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufAllocator + +public abstract class PacketCodec( + public val type: Class, + public val opcode: Int, + public val length: Int +) { + init { + require(opcode in 0 until 256) + require(length >= PacketLength.VARIABLE_SHORT) + } + + public abstract fun decode(input: ByteBuf): T + public abstract fun encode(input: T, output: ByteBuf) + + public open fun getLength(input: T): Int { + return length + } + + public fun allocateBuffer(alloc: ByteBufAllocator, input: T, preferDirect: Boolean): ByteBuf { + val payloadLen = getLength(input) + if (payloadLen < 0) { + return if (preferDirect) { + alloc.ioBuffer() + } else { + alloc.heapBuffer() + } + } + + val headerLen = when (length) { + PacketLength.VARIABLE_BYTE -> 2 + PacketLength.VARIABLE_SHORT -> 3 + else -> 1 + } + + val totalLen = headerLen + payloadLen + + return if (preferDirect) { + alloc.ioBuffer(totalLen, totalLen) + } else { + alloc.heapBuffer(totalLen, totalLen) + } + } +} diff --git a/protocol/src/main/kotlin/org/openrs2/protocol/PacketLength.kt b/protocol/src/main/kotlin/org/openrs2/protocol/PacketLength.kt new file mode 100644 index 00000000..b04d6a10 --- /dev/null +++ b/protocol/src/main/kotlin/org/openrs2/protocol/PacketLength.kt @@ -0,0 +1,6 @@ +package org.openrs2.protocol + +public object PacketLength { + public const val VARIABLE_SHORT: Int = -2 + public const val VARIABLE_BYTE: Int = -1 +} diff --git a/protocol/src/main/kotlin/org/openrs2/protocol/Protocol.kt b/protocol/src/main/kotlin/org/openrs2/protocol/Protocol.kt new file mode 100644 index 00000000..7d952de7 --- /dev/null +++ b/protocol/src/main/kotlin/org/openrs2/protocol/Protocol.kt @@ -0,0 +1,22 @@ +package org.openrs2.protocol + +public class Protocol(vararg codecs: PacketCodec<*>) { + private val decoders = arrayOfNulls>(256) + private val encoders = codecs.associateBy(PacketCodec<*>::type) + + init { + for (codec in codecs) { + decoders[codec.opcode] = codec + } + } + + public fun getDecoder(opcode: Int): PacketCodec<*>? { + require(opcode in decoders.indices) + return decoders[opcode] + } + + @Suppress("UNCHECKED_CAST") + public fun getEncoder(type: Class): PacketCodec? { + return encoders[type] as PacketCodec? + } +} diff --git a/protocol/src/main/kotlin/org/openrs2/protocol/Rs2Decoder.kt b/protocol/src/main/kotlin/org/openrs2/protocol/Rs2Decoder.kt new file mode 100644 index 00000000..bef9ce51 --- /dev/null +++ b/protocol/src/main/kotlin/org/openrs2/protocol/Rs2Decoder.kt @@ -0,0 +1,80 @@ +package org.openrs2.protocol + +import com.github.michaelbull.logging.InlineLogger +import io.netty.buffer.ByteBuf +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.ByteToMessageDecoder +import io.netty.handler.codec.DecoderException +import org.openrs2.crypto.NopStreamCipher +import org.openrs2.crypto.StreamCipher + +public class Rs2Decoder(public var protocol: Protocol) : ByteToMessageDecoder() { + private enum class State { + READ_OPCODE, + READ_LENGTH, + READ_PAYLOAD + } + + public var cipher: StreamCipher = NopStreamCipher + private var state = State.READ_OPCODE + private lateinit var decoder: PacketCodec<*> + private var length = 0 + + init { + isSingleDecode = true + } + + override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList) { + if (state == State.READ_OPCODE) { + if (!input.isReadable) { + return + } + + val opcode = (input.readUnsignedByte().toInt() - cipher.nextInt()) and 0xFF + decoder = protocol.getDecoder(opcode) ?: throw DecoderException("Unsupported opcode: $opcode") + length = decoder.length + + state = State.READ_LENGTH + } + + if (state == State.READ_LENGTH) { + when (length) { + PacketLength.VARIABLE_BYTE -> { + if (!input.isReadable) { + return + } + + length = input.readUnsignedByte().toInt() + } + PacketLength.VARIABLE_SHORT -> { + if (input.readableBytes() < 2) { + return + } + + length = input.readUnsignedShort() + } + } + + state = State.READ_PAYLOAD + } + + if (state == State.READ_PAYLOAD) { + if (input.readableBytes() < length) { + return + } + + out += try { + decoder.decode(input.readSlice(length)) + } catch (ex: NotImplementedError) { + // TODO(gpe): remove this catch block when every packet is implemented + logger.warn { "Skipping unimplemented packet: ${decoder.javaClass}" } + } + + state = State.READ_OPCODE + } + } + + private companion object { + private val logger = InlineLogger() + } +} diff --git a/protocol/src/main/kotlin/org/openrs2/protocol/Rs2Encoder.kt b/protocol/src/main/kotlin/org/openrs2/protocol/Rs2Encoder.kt new file mode 100644 index 00000000..813cdada --- /dev/null +++ b/protocol/src/main/kotlin/org/openrs2/protocol/Rs2Encoder.kt @@ -0,0 +1,60 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.EncoderException +import io.netty.handler.codec.MessageToByteEncoder +import org.openrs2.crypto.NopStreamCipher +import org.openrs2.crypto.StreamCipher + +public class Rs2Encoder(public var protocol: Protocol) : MessageToByteEncoder(Packet::class.java) { + public var cipher: StreamCipher = NopStreamCipher + + override fun encode(ctx: ChannelHandlerContext, msg: Packet, out: ByteBuf) { + val encoder = protocol.getEncoder(msg.javaClass) + ?: throw EncoderException("Unsupported packet type: ${msg.javaClass}") + + out.writeByte(encoder.opcode + cipher.nextInt()) + + val len = encoder.length + val lenIndex = out.writerIndex() + when (len) { + PacketLength.VARIABLE_BYTE -> out.writeZero(1) + PacketLength.VARIABLE_SHORT -> out.writeZero(2) + } + + val payloadIndex = out.writerIndex() + encoder.encode(msg, out) + + val written = out.writerIndex() - payloadIndex + + when (len) { + PacketLength.VARIABLE_BYTE -> { + if (written >= 256) { + throw EncoderException("Variable byte payload too long: $written bytes") + } + + out.setByte(lenIndex, written) + } + PacketLength.VARIABLE_SHORT -> { + if (written >= 65536) { + throw EncoderException("Variable short payload too long: $written bytes") + } + + out.setShort(lenIndex, written) + } + else -> { + if (written != len) { + throw EncoderException("Fixed payload length mismatch (expected $len bytes, got $written bytes)") + } + } + } + } + + override fun allocateBuffer(ctx: ChannelHandlerContext, msg: Packet, preferDirect: Boolean): ByteBuf { + val encoder = protocol.getEncoder(msg.javaClass) + ?: throw EncoderException("Unsupported packet type: ${msg.javaClass}") + + return encoder.allocateBuffer(ctx.alloc(), msg, preferDirect) + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/EmptyPacket.kt b/protocol/src/test/kotlin/org/openrs2/protocol/EmptyPacket.kt new file mode 100644 index 00000000..32c08765 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/EmptyPacket.kt @@ -0,0 +1,3 @@ +package org.openrs2.protocol + +object EmptyPacket : Packet diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/FixedPacket.kt b/protocol/src/test/kotlin/org/openrs2/protocol/FixedPacket.kt new file mode 100644 index 00000000..48da5293 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/FixedPacket.kt @@ -0,0 +1,3 @@ +package org.openrs2.protocol + +internal data class FixedPacket(val value: Int) : Packet diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/FixedPacketCodec.kt b/protocol/src/test/kotlin/org/openrs2/protocol/FixedPacketCodec.kt new file mode 100644 index 00000000..0f428c30 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/FixedPacketCodec.kt @@ -0,0 +1,18 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf + +internal object FixedPacketCodec : PacketCodec( + type = FixedPacket::class.java, + opcode = 0, + length = 4 +) { + override fun decode(input: ByteBuf): FixedPacket { + val value = input.readInt() + return FixedPacket(value) + } + + override fun encode(input: FixedPacket, output: ByteBuf) { + output.writeInt(input.value) + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/LengthMismatchPacketCodec.kt b/protocol/src/test/kotlin/org/openrs2/protocol/LengthMismatchPacketCodec.kt new file mode 100644 index 00000000..8a4009eb --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/LengthMismatchPacketCodec.kt @@ -0,0 +1,18 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf + +internal object LengthMismatchPacketCodec : PacketCodec( + type = FixedPacket::class.java, + opcode = 0, + length = 5 +) { + override fun decode(input: ByteBuf): FixedPacket { + val value = input.readInt() + return FixedPacket(value) + } + + override fun encode(input: FixedPacket, output: ByteBuf) { + output.writeInt(input.value) + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/Rs2DecoderTest.kt b/protocol/src/test/kotlin/org/openrs2/protocol/Rs2DecoderTest.kt new file mode 100644 index 00000000..75c0b63f --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/Rs2DecoderTest.kt @@ -0,0 +1,103 @@ +package org.openrs2.protocol + +import io.netty.buffer.Unpooled +import io.netty.channel.embedded.EmbeddedChannel +import io.netty.handler.codec.DecoderException +import org.junit.jupiter.api.assertThrows +import org.openrs2.buffer.wrappedBuffer +import kotlin.test.Test +import kotlin.test.assertEquals + +object Rs2DecoderTest { + @Test + fun testDecode() { + testDecode(byteArrayOf(0, 0x11, 0x22, 0x33, 0x44), FixedPacket(0x11223344)) + testDecode(byteArrayOf(1, 3, 0x11, 0x22, 0x33), VariableBytePacket(byteArrayOf(0x11, 0x22, 0x33))) + testDecode(byteArrayOf(2, 0, 3, 0x11, 0x22, 0x33), VariableShortPacket(byteArrayOf(0x11, 0x22, 0x33))) + testDecode(byteArrayOf(5), EmptyPacket) + } + + @Test + fun testFragmented() { + testFragmented(byteArrayOf(0, 0x11, 0x22, 0x33, 0x44), FixedPacket(0x11223344)) + testFragmented(byteArrayOf(1, 3, 0x11, 0x22, 0x33), VariableBytePacket(byteArrayOf(0x11, 0x22, 0x33))) + testFragmented(byteArrayOf(2, 0, 3, 0x11, 0x22, 0x33), VariableShortPacket(byteArrayOf(0x11, 0x22, 0x33))) + } + + @Test + fun testUnsupported() { + val channel = EmbeddedChannel(Rs2Decoder(Protocol())) + + assertThrows { + channel.writeInbound(wrappedBuffer(0)) + } + } + + @Test + fun testEncryptedOpcode() { + val decoder = Rs2Decoder(Protocol(FixedPacketCodec)) + decoder.cipher = TestStreamCipher + + val channel = EmbeddedChannel(decoder) + channel.writeInbound(wrappedBuffer(10, 0x11, 0x22, 0x33, 0x44)) + + val actual = channel.readInbound() + assertEquals(FixedPacket(0x11223344), actual) + } + + @Test + fun testSwitchProtocol() { + val decoder = Rs2Decoder(Protocol(FixedPacketCodec)) + val channel = EmbeddedChannel(decoder) + + channel.writeInbound(wrappedBuffer(0, 0x11, 0x22, 0x33, 0x44)) + channel.readInbound() + + assertThrows { + channel.writeInbound(wrappedBuffer(5)) + } + + decoder.protocol = Protocol(TestEmptyPacketCodec) + + channel.writeInbound(wrappedBuffer(5)) + + val actual = channel.readInbound() + assertEquals(EmptyPacket, actual) + + assertThrows { + channel.writeInbound(wrappedBuffer(0, 0x11, 0x22, 0x33, 0x44)) + } + } + + private fun testDecode(buf: ByteArray, expected: Packet) { + val channel = EmbeddedChannel(Rs2Decoder(Protocol( + FixedPacketCodec, + VariableBytePacketCodec, + VariableShortPacketCodec, + VariableByteOptimisedPacketCodec, + VariableShortOptimisedPacketCodec, + TestEmptyPacketCodec + ))) + channel.writeInbound(Unpooled.wrappedBuffer(buf)) + + val actual = channel.readInbound() + assertEquals(expected, actual) + } + + private fun testFragmented(buf: ByteArray, expected: Packet) { + val channel = EmbeddedChannel(Rs2Decoder(Protocol( + FixedPacketCodec, + VariableBytePacketCodec, + VariableShortPacketCodec, + VariableByteOptimisedPacketCodec, + VariableShortOptimisedPacketCodec + ))) + + for (b in buf) { + channel.writeInbound(wrappedBuffer(b)) + } + + val actual = channel.readInbound() + assertEquals(expected, actual) + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/Rs2EncoderTest.kt b/protocol/src/test/kotlin/org/openrs2/protocol/Rs2EncoderTest.kt new file mode 100644 index 00000000..5763b2d1 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/Rs2EncoderTest.kt @@ -0,0 +1,127 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.embedded.EmbeddedChannel +import io.netty.handler.codec.EncoderException +import org.junit.jupiter.api.assertThrows +import org.openrs2.buffer.use +import org.openrs2.buffer.wrappedBuffer +import kotlin.test.Test +import kotlin.test.assertEquals + +object Rs2EncoderTest { + @Test + fun testEncode() { + testEncode(FixedPacket(0x11223344), byteArrayOf(0, 0x11, 0x22, 0x33, 0x44)) + testEncode(VariableBytePacket(byteArrayOf(0x11, 0x22, 0x33)), byteArrayOf(1, 3, 0x11, 0x22, 0x33)) + testEncode(VariableShortPacket(byteArrayOf(0x11, 0x22, 0x33)), byteArrayOf(2, 0, 3, 0x11, 0x22, 0x33)) + testEncode(EmptyPacket, byteArrayOf(5)) + } + + @Test + fun testTooLong() { + val channel = EmbeddedChannel(Rs2Encoder(Protocol( + VariableBytePacketCodec, + VariableShortPacketCodec + ))) + + channel.writeOutbound(VariableShortPacket(ByteArray(255))) + channel.readOutbound().release() + + channel.writeOutbound(VariableShortPacket(ByteArray(65535))) + channel.readOutbound().release() + + assertThrows { + channel.writeOutbound(VariableBytePacket(ByteArray(256))) + } + + assertThrows { + channel.writeOutbound(VariableShortPacket(ByteArray(65536))) + } + } + + @Test + fun testUnsupported() { + val channel = EmbeddedChannel(Rs2Encoder(Protocol())) + + assertThrows { + channel.writeOutbound(FixedPacket(0x11223344)) + } + } + + @Test + fun testLengthMismatch() { + val channel = EmbeddedChannel(Rs2Encoder(Protocol(LengthMismatchPacketCodec))) + + assertThrows { + channel.writeOutbound(FixedPacket(0x11223344)) + } + } + + @Test + fun testLengthOptimised() { + testEncode(VariableByteOptimisedPacket(byteArrayOf(0x11, 0x22, 0x33)), byteArrayOf(3, 3, 0x11, 0x22, 0x33)) + testEncode(VariableShortOptimisedPacket(byteArrayOf(0x11, 0x22, 0x33)), byteArrayOf(4, 0, 3, 0x11, 0x22, 0x33)) + } + + @Test + fun testEncryptedOpcode() { + val encoder = Rs2Encoder(Protocol(FixedPacketCodec)) + encoder.cipher = TestStreamCipher + + val channel = EmbeddedChannel(encoder) + channel.writeOutbound(FixedPacket(0x11223344)) + + channel.readOutbound().use { actual -> + wrappedBuffer(10, 0x11, 0x22, 0x33, 0x44).use { expected -> + assertEquals(expected, actual) + } + } + } + + @Test + fun testSwitchProtocol() { + val encoder = Rs2Encoder(Protocol(FixedPacketCodec)) + val channel = EmbeddedChannel(encoder) + + channel.writeOutbound(FixedPacket(0x11223344)) + channel.readOutbound().release() + + assertThrows { + channel.writeOutbound(EmptyPacket) + } + + encoder.protocol = Protocol(TestEmptyPacketCodec) + + channel.writeOutbound(EmptyPacket) + + channel.readOutbound().use { actual -> + wrappedBuffer(5).use { expected -> + assertEquals(expected, actual) + } + } + + assertThrows { + channel.writeOutbound(FixedPacket(0x11223344)) + } + } + + private fun testEncode(packet: Packet, expected: ByteArray) { + val channel = EmbeddedChannel(Rs2Encoder(Protocol( + FixedPacketCodec, + VariableBytePacketCodec, + VariableShortPacketCodec, + VariableByteOptimisedPacketCodec, + VariableShortOptimisedPacketCodec, + TestEmptyPacketCodec + ))) + channel.writeOutbound(packet) + + channel.readOutbound().use { actual -> + Unpooled.wrappedBuffer(expected).use { expected -> + assertEquals(expected, actual) + } + } + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/TestEmptyPacketCodec.kt b/protocol/src/test/kotlin/org/openrs2/protocol/TestEmptyPacketCodec.kt new file mode 100644 index 00000000..ad95864e --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/TestEmptyPacketCodec.kt @@ -0,0 +1,6 @@ +package org.openrs2.protocol + +object TestEmptyPacketCodec : EmptyPacketCodec( + packet = EmptyPacket, + opcode = 5 +) diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/TestStreamCipher.kt b/protocol/src/test/kotlin/org/openrs2/protocol/TestStreamCipher.kt new file mode 100644 index 00000000..17d065ad --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/TestStreamCipher.kt @@ -0,0 +1,9 @@ +package org.openrs2.protocol + +import org.openrs2.crypto.StreamCipher + +object TestStreamCipher : StreamCipher { + override fun nextInt(): Int { + return 10 + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/VariableByteOptimisedPacket.kt b/protocol/src/test/kotlin/org/openrs2/protocol/VariableByteOptimisedPacket.kt new file mode 100644 index 00000000..ed300491 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/VariableByteOptimisedPacket.kt @@ -0,0 +1,3 @@ +package org.openrs2.protocol + +internal class VariableByteOptimisedPacket(val value: ByteArray) : Packet diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/VariableByteOptimisedPacketCodec.kt b/protocol/src/test/kotlin/org/openrs2/protocol/VariableByteOptimisedPacketCodec.kt new file mode 100644 index 00000000..0b56e87c --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/VariableByteOptimisedPacketCodec.kt @@ -0,0 +1,23 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf + +internal object VariableByteOptimisedPacketCodec : PacketCodec( + type = VariableByteOptimisedPacket::class.java, + opcode = 3, + length = PacketLength.VARIABLE_BYTE +) { + override fun decode(input: ByteBuf): VariableByteOptimisedPacket { + val value = ByteArray(input.readableBytes()) + input.readBytes(value) + return VariableByteOptimisedPacket(value) + } + + override fun encode(input: VariableByteOptimisedPacket, output: ByteBuf) { + output.writeBytes(input.value) + } + + override fun getLength(input: VariableByteOptimisedPacket): Int { + return input.value.size + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/VariableBytePacket.kt b/protocol/src/test/kotlin/org/openrs2/protocol/VariableBytePacket.kt new file mode 100644 index 00000000..ed5bf46e --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/VariableBytePacket.kt @@ -0,0 +1,18 @@ +package org.openrs2.protocol + +internal class VariableBytePacket(val value: ByteArray) : Packet { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as VariableBytePacket + + if (!value.contentEquals(other.value)) return false + + return true + } + + override fun hashCode(): Int { + return value.contentHashCode() + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/VariableBytePacketCodec.kt b/protocol/src/test/kotlin/org/openrs2/protocol/VariableBytePacketCodec.kt new file mode 100644 index 00000000..46bc4974 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/VariableBytePacketCodec.kt @@ -0,0 +1,19 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf + +internal object VariableBytePacketCodec : PacketCodec( + type = VariableBytePacket::class.java, + opcode = 1, + length = PacketLength.VARIABLE_BYTE +) { + override fun decode(input: ByteBuf): VariableBytePacket { + val value = ByteArray(input.readableBytes()) + input.readBytes(value) + return VariableBytePacket(value) + } + + override fun encode(input: VariableBytePacket, output: ByteBuf) { + output.writeBytes(input.value) + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortOptimisedPacket.kt b/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortOptimisedPacket.kt new file mode 100644 index 00000000..b3e7a135 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortOptimisedPacket.kt @@ -0,0 +1,3 @@ +package org.openrs2.protocol + +internal class VariableShortOptimisedPacket(val value: ByteArray) : Packet diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortOptimisedPacketCodec.kt b/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortOptimisedPacketCodec.kt new file mode 100644 index 00000000..1f719a2c --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortOptimisedPacketCodec.kt @@ -0,0 +1,23 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf + +internal object VariableShortOptimisedPacketCodec : PacketCodec( + type = VariableShortOptimisedPacket::class.java, + opcode = 4, + length = PacketLength.VARIABLE_SHORT +) { + override fun decode(input: ByteBuf): VariableShortOptimisedPacket { + val value = ByteArray(input.readableBytes()) + input.readBytes(value) + return VariableShortOptimisedPacket(value) + } + + override fun encode(input: VariableShortOptimisedPacket, output: ByteBuf) { + output.writeBytes(input.value) + } + + override fun getLength(input: VariableShortOptimisedPacket): Int { + return input.value.size + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortPacket.kt b/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortPacket.kt new file mode 100644 index 00000000..6c364de3 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortPacket.kt @@ -0,0 +1,18 @@ +package org.openrs2.protocol + +internal class VariableShortPacket(val value: ByteArray) : Packet { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as VariableShortPacket + + if (!value.contentEquals(other.value)) return false + + return true + } + + override fun hashCode(): Int { + return value.contentHashCode() + } +} diff --git a/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortPacketCodec.kt b/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortPacketCodec.kt new file mode 100644 index 00000000..76d74607 --- /dev/null +++ b/protocol/src/test/kotlin/org/openrs2/protocol/VariableShortPacketCodec.kt @@ -0,0 +1,19 @@ +package org.openrs2.protocol + +import io.netty.buffer.ByteBuf + +internal object VariableShortPacketCodec : PacketCodec( + type = VariableShortPacket::class.java, + opcode = 2, + length = PacketLength.VARIABLE_SHORT +) { + override fun decode(input: ByteBuf): VariableShortPacket { + val value = ByteArray(input.readableBytes()) + input.readBytes(value) + return VariableShortPacket(value) + } + + override fun encode(input: VariableShortPacket, output: ByteBuf) { + output.writeBytes(input.value) + } +}