Open-source multiplayer game server compatible with the RuneScape client https://www.openrs2.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
openrs2/protocol/src/main/kotlin/org/openrs2/protocol/js5/downstream/Js5ResponseEncoder.kt

111 lines
3.8 KiB

package org.openrs2.protocol.js5.downstream
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 object 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 or 0x80
}
out.writeByte(compression)
out.writeBytes(msg.data, min(msg.data.readableBytes(), 508))
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()
/*
* The naive code to estimate the length is:
*
* var len = 3 // for the archive/group header
* len += dataLen // for the data itself
*
* // first 0xFF marker
* if (dataLen > 509) { // compression byte plus the first 508 bytes of data
* len++
* dataLen -= 509
*
* // remaining 0xFF markers
* while (dataLen > 511) {
* len++
* dataLen -= 511
* }
* }
*
* We can simplify this by combining the if and while loop by adding an
* extra 2 bytes to dataLen, which ensures we add the first marker
* after 509 bytes (511-2) and then all subsequent markers after 511
* bytes:
*
* var len = 3
* len += dataLen
*
* dataLen += 2
*
* while (dataLen > 511) {
* len++
* dataLen -= 511
* }
*
* The while loop can be replaced with division:
*
* var len = 3
* len += dataLen
* len += divide(dataLen + 2, 511)
*
* divide() is almost but not quite standard division. We want
* divide(x, 511) to produce 0 if x=511, and only produce 1 if x=512.
* Similarly, it should produce 1 if x=1022, and only produce 1 if
* x=1023, and so on.
*
* This can be achieved by implementing divide(x, y) as
* ((x + (y - 1)) / y) - 1. So in our case, where y=511,
* ((x + 510) / y) - 1.
*
* Combined with the above, our length calculation becomes:
*
* var len = 3
* len += dataLen
* len += (dataLen + 2 + 510) / 511 - 1
*
* which we can simplify to:
*
* val len = 2 + dataLen + (512 + dataLen) / 511
*
* As this is confusing, testAllocateBuffer() has a test to ensure the
* length calculation is correct for all dataLens between 1 and 1534
* inclusive, which covers all lengths for 1, 2 and 3 block responses
* completely, and one length for a 4 block responses (as a boundary
* check).
*/
val len = 2 + dataLen + (512 + dataLen) / 511
return if (preferDirect) {
ctx.alloc().ioBuffer(len, len)
} else {
ctx.alloc().heapBuffer(len, len)
}
}
}