forked from openrs2/openrs2
parent
0e706bc578
commit
5036eb3da8
@ -0,0 +1,28 @@ |
|||||||
|
plugins { |
||||||
|
`maven-publish` |
||||||
|
kotlin("jvm") |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
api("io.netty:netty-codec:${Versions.netty}") |
||||||
|
|
||||||
|
implementation(project(":buffer")) |
||||||
|
|
||||||
|
testImplementation(project(":buffer")) |
||||||
|
} |
||||||
|
|
||||||
|
publishing { |
||||||
|
publications.create<MavenPublication>("maven") { |
||||||
|
from(components["java"]) |
||||||
|
|
||||||
|
pom { |
||||||
|
packaging = "jar" |
||||||
|
name.set("OpenRS2 Protocol") |
||||||
|
description.set( |
||||||
|
""" |
||||||
|
An implementation of the RuneScape protocol. |
||||||
|
""".trimIndent() |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
public sealed class Js5Request { |
||||||
|
public data class Group( |
||||||
|
public val prefetch: Boolean, |
||||||
|
public val archive: Int, |
||||||
|
public val group: Int |
||||||
|
) : Js5Request() |
||||||
|
|
||||||
|
public object LoggedIn : Js5Request() |
||||||
|
public object LoggedOut : Js5Request() |
||||||
|
public data class Rekey(public val key: Int) : Js5Request() |
||||||
|
public object Connected : Js5Request() |
||||||
|
public object Disconnect : Js5Request() |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.channel.ChannelHandlerContext |
||||||
|
import io.netty.handler.codec.ByteToMessageDecoder |
||||||
|
import io.netty.handler.codec.DecoderException |
||||||
|
|
||||||
|
public class Js5RequestDecoder : ByteToMessageDecoder() { |
||||||
|
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) { |
||||||
|
if (input.readableBytes() < 4) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
val opcode = input.readUnsignedByte().toInt() |
||||||
|
|
||||||
|
if (opcode == 0 || opcode == 1) { |
||||||
|
val archive = input.readUnsignedByte().toInt() |
||||||
|
val group = input.readUnsignedShort() |
||||||
|
out += Js5Request.Group(opcode == 0, archive, group) |
||||||
|
} else if (opcode == 4) { |
||||||
|
val key = input.readUnsignedByte().toInt() |
||||||
|
input.skipBytes(2) |
||||||
|
out += Js5Request.Rekey(key) |
||||||
|
} else { |
||||||
|
input.skipBytes(3) |
||||||
|
out += when (opcode) { |
||||||
|
2 -> Js5Request.LoggedIn |
||||||
|
3 -> Js5Request.LoggedOut |
||||||
|
6 -> Js5Request.Connected |
||||||
|
7 -> Js5Request.Disconnect |
||||||
|
else -> throw DecoderException("Unknown JS5 opcode: $opcode") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.channel.ChannelHandler |
||||||
|
import io.netty.channel.ChannelHandlerContext |
||||||
|
import io.netty.handler.codec.MessageToByteEncoder |
||||||
|
|
||||||
|
@ChannelHandler.Sharable |
||||||
|
public class Js5RequestEncoder : MessageToByteEncoder<Js5Request>(Js5Request::class.java) { |
||||||
|
override fun encode(ctx: ChannelHandlerContext, msg: Js5Request, out: ByteBuf) { |
||||||
|
when (msg) { |
||||||
|
is Js5Request.Group -> { |
||||||
|
out.writeByte(if (msg.prefetch) 0 else 1) |
||||||
|
out.writeByte(msg.archive) |
||||||
|
out.writeShort(msg.group) |
||||||
|
} |
||||||
|
is Js5Request.Rekey -> { |
||||||
|
out.writeByte(4) |
||||||
|
out.writeByte(msg.key) |
||||||
|
out.writeZero(2) |
||||||
|
} |
||||||
|
is Js5Request.LoggedIn -> encodeSimple(out, 2) |
||||||
|
is Js5Request.LoggedOut -> encodeSimple(out, 3) |
||||||
|
is Js5Request.Connected -> { |
||||||
|
out.writeByte(6) |
||||||
|
out.writeMedium(3) |
||||||
|
} |
||||||
|
is Js5Request.Disconnect -> encodeSimple(out, 7) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun encodeSimple(out: ByteBuf, opcode: Int) { |
||||||
|
out.writeByte(opcode) |
||||||
|
out.writeZero(3) |
||||||
|
} |
||||||
|
|
||||||
|
override fun allocateBuffer(ctx: ChannelHandlerContext, msg: Js5Request, preferDirect: Boolean): ByteBuf { |
||||||
|
return if (preferDirect) { |
||||||
|
ctx.alloc().ioBuffer(4, 4) |
||||||
|
} else { |
||||||
|
ctx.alloc().heapBuffer(4, 4) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.util.ReferenceCounted |
||||||
|
|
||||||
|
public data class Js5Response( |
||||||
|
public val prefetch: Boolean, |
||||||
|
public val archive: Int, |
||||||
|
public val group: Int, |
||||||
|
public val data: ByteBuf |
||||||
|
) : ReferenceCounted { |
||||||
|
override fun refCnt(): Int { |
||||||
|
return data.refCnt() |
||||||
|
} |
||||||
|
|
||||||
|
override fun retain(): Js5Response { |
||||||
|
data.retain() |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
override fun retain(increment: Int): Js5Response { |
||||||
|
data.retain(increment) |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
override fun touch(): Js5Response { |
||||||
|
data.touch() |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
override fun touch(hint: Any?): Js5Response { |
||||||
|
data.touch(hint) |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
override fun release(): Boolean { |
||||||
|
return data.release() |
||||||
|
} |
||||||
|
|
||||||
|
override fun release(decrement: Int): Boolean { |
||||||
|
return data.release(decrement) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.buffer.Unpooled |
||||||
|
import io.netty.channel.ChannelHandlerContext |
||||||
|
import io.netty.handler.codec.ByteToMessageDecoder |
||||||
|
import io.netty.handler.codec.DecoderException |
||||||
|
import kotlin.math.min |
||||||
|
|
||||||
|
public class Js5ResponseDecoder : ByteToMessageDecoder() { |
||||||
|
private enum class State { |
||||||
|
READ_HEADER, |
||||||
|
READ_DATA |
||||||
|
} |
||||||
|
|
||||||
|
private var state = State.READ_HEADER |
||||||
|
private var prefetch: Boolean = false |
||||||
|
private var archive: Int = 0 |
||||||
|
private var group: Int = 0 |
||||||
|
private var type: Int = 0 |
||||||
|
private var data: ByteBuf = Unpooled.EMPTY_BUFFER |
||||||
|
|
||||||
|
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) { |
||||||
|
if (state == State.READ_HEADER) { |
||||||
|
if (input.readableBytes() < 8) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
archive = input.readUnsignedByte().toInt() |
||||||
|
group = input.readUnsignedShort() |
||||||
|
type = input.readUnsignedByte().toInt() |
||||||
|
|
||||||
|
if (type and 0x80 != 0) { |
||||||
|
prefetch = true |
||||||
|
type = type and 0x80.inv() |
||||||
|
} else { |
||||||
|
prefetch = false |
||||||
|
} |
||||||
|
|
||||||
|
val len = input.readInt() |
||||||
|
if (len < 0) { |
||||||
|
throw DecoderException("Length is negative: $len") |
||||||
|
} |
||||||
|
|
||||||
|
val totalLen = if (type == 0) { |
||||||
|
len + 5 |
||||||
|
} else { |
||||||
|
len + 9 |
||||||
|
} |
||||||
|
|
||||||
|
if (totalLen < 0) { |
||||||
|
throw DecoderException("Total length exceeds maximum ByteBuf size") |
||||||
|
} |
||||||
|
|
||||||
|
// TODO(gpe): release data here? |
||||||
|
data = ctx.alloc().buffer(totalLen, totalLen) |
||||||
|
data.writeByte(type) |
||||||
|
data.writeInt(len) |
||||||
|
|
||||||
|
state = State.READ_DATA |
||||||
|
} |
||||||
|
|
||||||
|
if (state == State.READ_DATA) { |
||||||
|
while (data.isWritable) { |
||||||
|
val blockLen = min(511 - ((data.readableBytes() + 3) % 511), data.writableBytes()) |
||||||
|
val last = data.writableBytes() <= blockLen |
||||||
|
|
||||||
|
val blockLenIncludingTrailer = if (last) { |
||||||
|
blockLen |
||||||
|
} else { |
||||||
|
blockLen + 1 |
||||||
|
} |
||||||
|
|
||||||
|
if (input.readableBytes() < blockLenIncludingTrailer) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
data.writeBytes(input, blockLen) |
||||||
|
|
||||||
|
if (!last && input.readUnsignedByte().toInt() != 0xFF) { |
||||||
|
throw DecoderException("Invalid block trailer") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
out += Js5Response(prefetch, archive, group, data) |
||||||
|
|
||||||
|
data = Unpooled.EMPTY_BUFFER |
||||||
|
|
||||||
|
state = State.READ_HEADER |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun handlerRemoved0(ctx: ChannelHandlerContext?) { |
||||||
|
data.release() |
||||||
|
data = Unpooled.EMPTY_BUFFER |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.channel.ChannelHandler |
||||||
|
import io.netty.channel.ChannelHandlerContext |
||||||
|
import io.netty.handler.codec.EncoderException |
||||||
|
import io.netty.handler.codec.MessageToByteEncoder |
||||||
|
import kotlin.math.min |
||||||
|
|
||||||
|
@ChannelHandler.Sharable |
||||||
|
public class Js5ResponseEncoder : MessageToByteEncoder<Js5Response>(Js5Response::class.java) { |
||||||
|
override fun encode(ctx: ChannelHandlerContext, msg: Js5Response, out: ByteBuf) { |
||||||
|
out.writeByte(msg.archive) |
||||||
|
out.writeShort(msg.group) |
||||||
|
|
||||||
|
if (!msg.data.isReadable) { |
||||||
|
// TOOD(gpe): check if the entire container is well-formed? |
||||||
|
throw EncoderException("Missing compression byte") |
||||||
|
} |
||||||
|
|
||||||
|
var compression = msg.data.readUnsignedByte().toInt() |
||||||
|
if (msg.prefetch) { |
||||||
|
compression = compression xor 0x80 |
||||||
|
} |
||||||
|
out.writeByte(compression) |
||||||
|
|
||||||
|
out.writeBytes(msg.data, min(msg.data.readableBytes(), 507)) |
||||||
|
|
||||||
|
while (msg.data.isReadable) { |
||||||
|
out.writeByte(0xFF) |
||||||
|
out.writeBytes(msg.data, min(msg.data.readableBytes(), 511)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun allocateBuffer(ctx: ChannelHandlerContext, msg: Js5Response, preferDirect: Boolean): ByteBuf { |
||||||
|
val dataLen = msg.data.readableBytes() |
||||||
|
val len = 3 + dataLen + (3 + dataLen) / 511 |
||||||
|
|
||||||
|
return if (preferDirect) { |
||||||
|
ctx.alloc().ioBuffer(len, len) |
||||||
|
} else { |
||||||
|
ctx.alloc().heapBuffer(len, len) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.channel.ChannelHandlerContext |
||||||
|
import io.netty.handler.codec.MessageToMessageDecoder |
||||||
|
|
||||||
|
public class XorDecoder : MessageToMessageDecoder<ByteBuf>(ByteBuf::class.java) { |
||||||
|
public var key: Int = 0 |
||||||
|
|
||||||
|
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) { |
||||||
|
out += msg.xor(key) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.channel.ChannelHandlerContext |
||||||
|
import io.netty.handler.codec.MessageToMessageEncoder |
||||||
|
|
||||||
|
public class XorEncoder : MessageToMessageEncoder<ByteBuf>(ByteBuf::class.java) { |
||||||
|
public var key: Int = 0 |
||||||
|
|
||||||
|
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) { |
||||||
|
out += msg.xor(key) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
|
||||||
|
internal fun ByteBuf.xor(key: Int): ByteBuf { |
||||||
|
if (key == 0) { |
||||||
|
return retain() |
||||||
|
} |
||||||
|
|
||||||
|
val buf = if (refCnt() == 1) { |
||||||
|
retain() |
||||||
|
} else { |
||||||
|
copy() |
||||||
|
} |
||||||
|
|
||||||
|
if (buf.hasArray()) { |
||||||
|
val array = buf.array() |
||||||
|
|
||||||
|
val off = buf.arrayOffset() + buf.readerIndex() |
||||||
|
val len = buf.readableBytes() |
||||||
|
|
||||||
|
for (i in off until off + len) { |
||||||
|
array[i] = (array[i].toInt() xor key).toByte() |
||||||
|
} |
||||||
|
} else { |
||||||
|
val off = buf.readerIndex() |
||||||
|
val len = buf.readableBytes() |
||||||
|
|
||||||
|
for (i in off until off + len) { |
||||||
|
buf.setByte(i, buf.getByte(i).toInt() xor key) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return buf |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.Unpooled |
||||||
|
import io.netty.channel.embedded.EmbeddedChannel |
||||||
|
import io.netty.handler.codec.DecoderException |
||||||
|
import org.junit.jupiter.api.assertThrows |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
object Js5RequestDecoderTest { |
||||||
|
@Test |
||||||
|
fun testDecode() { |
||||||
|
testDecode(byteArrayOf(0, 2, 0, 3), Js5Request.Group(true, 2, 3)) |
||||||
|
testDecode(byteArrayOf(1, 2, 0, 3), Js5Request.Group(false, 2, 3)) |
||||||
|
testDecode(byteArrayOf(4, 0x55, 0, 0), Js5Request.Rekey(0x55)) |
||||||
|
testDecode(byteArrayOf(2, 0, 0, 0), Js5Request.LoggedIn) |
||||||
|
testDecode(byteArrayOf(3, 0, 0, 0), Js5Request.LoggedOut) |
||||||
|
testDecode(byteArrayOf(6, 0, 0, 3), Js5Request.Connected) |
||||||
|
testDecode(byteArrayOf(7, 0, 0, 0), Js5Request.Disconnect) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testFragmented() { |
||||||
|
val channel = EmbeddedChannel(Js5RequestDecoder()) |
||||||
|
channel.writeInbound(Unpooled.wrappedBuffer(byteArrayOf(0, 2))) |
||||||
|
channel.writeInbound(Unpooled.wrappedBuffer(byteArrayOf(0, 3))) |
||||||
|
assertEquals(Js5Request.Group(true, 2, 3), channel.readInbound()) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testUnknownOpcode() { |
||||||
|
val channel = EmbeddedChannel(Js5RequestDecoder()) |
||||||
|
|
||||||
|
assertThrows<DecoderException> { |
||||||
|
channel.writeInbound(Unpooled.wrappedBuffer(byteArrayOf(8, 0, 0, 0))) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun testDecode(bytes: ByteArray, expected: Js5Request) { |
||||||
|
val channel = EmbeddedChannel(Js5RequestDecoder()) |
||||||
|
|
||||||
|
channel.writeInbound(Unpooled.wrappedBuffer(bytes)) |
||||||
|
assertEquals(expected, channel.readInbound()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.buffer.Unpooled |
||||||
|
import io.netty.channel.embedded.EmbeddedChannel |
||||||
|
import org.openrs2.buffer.use |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
object Js5RequestEncoderTest { |
||||||
|
@Test |
||||||
|
fun testEncode() { |
||||||
|
testEncode(Js5Request.Group(true, 2, 3), byteArrayOf(0, 2, 0, 3)) |
||||||
|
testEncode(Js5Request.Group(false, 2, 3), byteArrayOf(1, 2, 0, 3)) |
||||||
|
testEncode(Js5Request.Rekey(0x55), byteArrayOf(4, 0x55, 0, 0)) |
||||||
|
testEncode(Js5Request.LoggedIn, byteArrayOf(2, 0, 0, 0)) |
||||||
|
testEncode(Js5Request.LoggedOut, byteArrayOf(3, 0, 0, 0)) |
||||||
|
testEncode(Js5Request.Connected, byteArrayOf(6, 0, 0, 3)) |
||||||
|
testEncode(Js5Request.Disconnect, byteArrayOf(7, 0, 0, 0)) |
||||||
|
} |
||||||
|
|
||||||
|
private fun testEncode(request: Js5Request, expected: ByteArray) { |
||||||
|
val channel = EmbeddedChannel(Js5RequestEncoder()) |
||||||
|
channel.writeOutbound(request) |
||||||
|
|
||||||
|
channel.readOutbound<ByteBuf>().use { actual -> |
||||||
|
Unpooled.wrappedBuffer(expected).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,138 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
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.use |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
object Js5ResponseDecoderTest { |
||||||
|
@Test |
||||||
|
fun testDecode() { |
||||||
|
testDecode("508.dat", "508-prefetch.dat", true) |
||||||
|
testDecode("508.dat", "508-urgent.dat", false) |
||||||
|
|
||||||
|
testDecode("509.dat", "509-prefetch.dat", true) |
||||||
|
testDecode("509.dat", "509-urgent.dat", false) |
||||||
|
|
||||||
|
testDecode("1019.dat", "1019-prefetch.dat", true) |
||||||
|
testDecode("1019.dat", "1019-urgent.dat", false) |
||||||
|
|
||||||
|
testDecode("1020.dat", "1020-prefetch.dat", true) |
||||||
|
testDecode("1020.dat", "1020-urgent.dat", false) |
||||||
|
|
||||||
|
testDecode("1530.dat", "1530-prefetch.dat", true) |
||||||
|
testDecode("1530.dat", "1530-urgent.dat", false) |
||||||
|
|
||||||
|
testDecode("1531.dat", "1531-prefetch.dat", true) |
||||||
|
testDecode("1531.dat", "1531-urgent.dat", false) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testDecodeFragmented() { |
||||||
|
val channel = EmbeddedChannel(Js5ResponseDecoder()) |
||||||
|
|
||||||
|
channel.writeInbound(Unpooled.wrappedBuffer(byteArrayOf(2, 0, 3, 0, 0, 0, 0))) |
||||||
|
channel.writeInbound( |
||||||
|
Unpooled.wrappedBuffer( |
||||||
|
byteArrayOf( |
||||||
|
7, |
||||||
|
'O'.toByte(), |
||||||
|
'p'.toByte(), |
||||||
|
'e'.toByte(), |
||||||
|
'n'.toByte() |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
channel.writeInbound(Unpooled.wrappedBuffer(byteArrayOf('R'.toByte(), 'S'.toByte(), '2'.toByte()))) |
||||||
|
|
||||||
|
Unpooled.buffer().use { buf -> |
||||||
|
buf.writeByte(0) |
||||||
|
buf.writeInt(7) |
||||||
|
buf.writeCharSequence("OpenRS2", Charsets.UTF_8) |
||||||
|
|
||||||
|
val expected = Js5Response(false, 2, 3, buf) |
||||||
|
|
||||||
|
channel.readInbound<Js5Response>().use { actual -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testDecodeNegativeLength() { |
||||||
|
val channel = EmbeddedChannel(Js5ResponseDecoder()) |
||||||
|
|
||||||
|
assertThrows<DecoderException> { |
||||||
|
channel.writeInbound( |
||||||
|
Unpooled.wrappedBuffer( |
||||||
|
byteArrayOf( |
||||||
|
2, 0, 3, 0, 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte() |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testDecodeOverflowUncompressed() { |
||||||
|
val channel = EmbeddedChannel(Js5ResponseDecoder()) |
||||||
|
|
||||||
|
assertThrows<DecoderException> { |
||||||
|
channel.writeInbound( |
||||||
|
Unpooled.wrappedBuffer( |
||||||
|
byteArrayOf( |
||||||
|
2, 0, 3, 0, 0x7F, 0xFF.toByte(), 0xFF.toByte(), 0xFB.toByte() |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testDecodeOverflowCompressed() { |
||||||
|
val channel = EmbeddedChannel(Js5ResponseDecoder()) |
||||||
|
|
||||||
|
assertThrows<DecoderException> { |
||||||
|
channel.writeInbound( |
||||||
|
Unpooled.wrappedBuffer( |
||||||
|
byteArrayOf( |
||||||
|
2, 0, 3, 1, 0x7F, 0xFF.toByte(), 0xFF.toByte(), 0xF7.toByte() |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testDecodeInvalidBlockTrailer() { |
||||||
|
val channel = EmbeddedChannel(Js5ResponseDecoder()) |
||||||
|
|
||||||
|
assertThrows<DecoderException> { |
||||||
|
channel.writeInbound(read("invalid-block-trailer.dat")) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun testDecode(container: String, encoded: String, prefetch: Boolean) { |
||||||
|
val channel = EmbeddedChannel(Js5ResponseDecoder()) |
||||||
|
|
||||||
|
channel.writeInbound(read(encoded)) |
||||||
|
|
||||||
|
read(container).use { data -> |
||||||
|
val expected = Js5Response(prefetch, 2, 3, data) |
||||||
|
|
||||||
|
channel.readInbound<Js5Response>().use { actual -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun read(name: String): ByteBuf { |
||||||
|
Js5ResponseDecoderTest::class.java.getResourceAsStream(name).use { input -> |
||||||
|
return Unpooled.wrappedBuffer(input.readAllBytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
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 kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
object Js5ResponseEncoderTest { |
||||||
|
@Test |
||||||
|
fun testEncode() { |
||||||
|
testEncode("508.dat", "508-prefetch.dat", true) |
||||||
|
testEncode("508.dat", "508-urgent.dat", false) |
||||||
|
|
||||||
|
testEncode("509.dat", "509-prefetch.dat", true) |
||||||
|
testEncode("509.dat", "509-urgent.dat", false) |
||||||
|
|
||||||
|
testEncode("1019.dat", "1019-prefetch.dat", true) |
||||||
|
testEncode("1019.dat", "1019-urgent.dat", false) |
||||||
|
|
||||||
|
testEncode("1020.dat", "1020-prefetch.dat", true) |
||||||
|
testEncode("1020.dat", "1020-urgent.dat", false) |
||||||
|
|
||||||
|
testEncode("1530.dat", "1530-prefetch.dat", true) |
||||||
|
testEncode("1530.dat", "1530-urgent.dat", false) |
||||||
|
|
||||||
|
testEncode("1531.dat", "1531-prefetch.dat", true) |
||||||
|
testEncode("1531.dat", "1531-urgent.dat", false) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testEncodeEmpty() { |
||||||
|
val channel = EmbeddedChannel(Js5ResponseEncoder()) |
||||||
|
|
||||||
|
assertThrows<EncoderException> { |
||||||
|
channel.writeOutbound(Js5Response(true, 2, 3, Unpooled.EMPTY_BUFFER)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun testEncode(container: String, encoded: String, prefetch: Boolean) { |
||||||
|
val channel = EmbeddedChannel(Js5ResponseEncoder()) |
||||||
|
|
||||||
|
read(container).use { buf -> |
||||||
|
channel.writeOutbound(Js5Response(prefetch, 2, 3, buf.retain())) |
||||||
|
} |
||||||
|
|
||||||
|
read(encoded).use { expected -> |
||||||
|
assertEquals(expected, channel.readOutbound()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun read(name: String): ByteBuf { |
||||||
|
Js5ResponseEncoderTest::class.java.getResourceAsStream(name).use { input -> |
||||||
|
return Unpooled.wrappedBuffer(input.readAllBytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.buffer.PooledByteBufAllocator |
||||||
|
import io.netty.buffer.Unpooled |
||||||
|
import io.netty.channel.embedded.EmbeddedChannel |
||||||
|
import org.openrs2.buffer.use |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
object XorDecoderTest { |
||||||
|
@Test |
||||||
|
fun testDecode() { |
||||||
|
testDecode(0, "OpenRS2", false) |
||||||
|
testDecode(0, "OpenRS2", true) |
||||||
|
testDecode(32, "oPENrs\u0012", false) |
||||||
|
testDecode(32, "oPENrs\u0012", true) |
||||||
|
} |
||||||
|
|
||||||
|
private fun testDecode(key: Int, expected: String, direct: Boolean) { |
||||||
|
val decoder = XorDecoder() |
||||||
|
decoder.key = key |
||||||
|
|
||||||
|
val channel = EmbeddedChannel(decoder) |
||||||
|
if (direct) { |
||||||
|
PooledByteBufAllocator.DEFAULT.ioBuffer().use { buf -> |
||||||
|
buf.writeBytes("OpenRS2".toByteArray()) |
||||||
|
channel.writeInbound(buf.retain()) |
||||||
|
} |
||||||
|
} else { |
||||||
|
channel.writeInbound(Unpooled.wrappedBuffer("OpenRS2".toByteArray())) |
||||||
|
} |
||||||
|
|
||||||
|
channel.readInbound<ByteBuf>().use { actual -> |
||||||
|
Unpooled.wrappedBuffer(expected.toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
package org.openrs2.protocol.js5 |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.buffer.PooledByteBufAllocator |
||||||
|
import io.netty.buffer.Unpooled |
||||||
|
import io.netty.channel.embedded.EmbeddedChannel |
||||||
|
import org.openrs2.buffer.use |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
object XorEncoderTest { |
||||||
|
@Test |
||||||
|
fun testEncode() { |
||||||
|
testEncode(0, "OpenRS2", false) |
||||||
|
testEncode(0, "OpenRS2", true) |
||||||
|
testEncode(32, "oPENrs\u0012", false) |
||||||
|
testEncode(32, "oPENrs\u0012", true) |
||||||
|
} |
||||||
|
|
||||||
|
private fun testEncode(key: Int, expected: String, direct: Boolean) { |
||||||
|
val encoder = XorEncoder() |
||||||
|
encoder.key = key |
||||||
|
|
||||||
|
val channel = EmbeddedChannel(encoder) |
||||||
|
if (direct) { |
||||||
|
PooledByteBufAllocator.DEFAULT.ioBuffer().use { buf -> |
||||||
|
buf.writeBytes("OpenRS2".toByteArray()) |
||||||
|
channel.writeOutbound(buf.retain()) |
||||||
|
} |
||||||
|
} else { |
||||||
|
channel.writeOutbound(Unpooled.wrappedBuffer("OpenRS2".toByteArray())) |
||||||
|
} |
||||||
|
|
||||||
|
channel.readOutbound<ByteBuf>().use { actual -> |
||||||
|
Unpooled.wrappedBuffer(expected.toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue