Initial commit

This commit is contained in:
2024-09-01 10:25:04 +02:00
commit 657331dd31
22 changed files with 1024 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
package nl.astraeus.tba
enum class DataType(val size: Int) {
BYTE(1),
SHORT(2),
INT(4),
LONG(8),
FLOAT(4),
DOUBLE(8),
STRING(-1), // max length 65535
CLOB(-1), // max length 2^32-1
BLOB(-1),
ARRAY(-1),
;
}
class Type(
val name: String,
val type: DataType,
val size: Int = if (type.size == -1) {
throw IllegalArgumentException("Size must be defined for type ${type.name}")
} else {
type.size
},
)
interface ByteArrayDefinition {
val types: List<Type>
val size: Int
}
open class BaseByteArrayDefinition(
vararg types: Type,
) : ByteArrayDefinition{
override val types: List<Type> = types.toList()
override val size: Int by lazy {
this.types.sumOf { it.size }
}
}
interface BinaryType {
val name: String
val typeId: Long
}

View File

@@ -0,0 +1,69 @@
package nl.astraeus.tba
fun ByteArray.setShort(index: Int, value: Short) {
this[index] = (value.toInt() shr 8).toByte()
this[index + 1] = value.toByte()
}
fun ByteArray.getShort(index: Int): Short {
return ((this[index].toInt() shl 8) or (this[index + 1].toInt() and 0xff)).toShort()
}
fun ByteArray.setInt(index: Int, value: Int) {
this[index] = ((value shr 24) and 0xff).toByte()
this[index + 1] = ((value shr 16) and 0xff).toByte()
this[index + 2] = ((value shr 8) and 0xff).toByte()
this[index + 3] = (value and 0xff).toByte()
}
fun ByteArray.getInt(index: Int): Int {
return (
(this[index].toInt() shl 24) or
((this[index+1].toInt() shl 16) and 0xff0000) or
((this[index+2].toInt() shl 8) and 0xff00) or
(this[index+3].toInt() and 0xff)
)
}
fun ByteArray.setLong(index: Int, value: Long) {
this[index] = ((value shr 56) and 0xff).toByte()
this[index + 1] = ((value shr 48) and 0xff).toByte()
this[index + 2] = ((value shr 40) and 0xff).toByte()
this[index + 3] = ((value shr 32) and 0xff).toByte()
this[index + 4] = ((value shr 24) and 0xff).toByte()
this[index + 5] = ((value shr 16) and 0xff).toByte()
this[index + 6] = ((value shr 8) and 0xff).toByte()
this[index + 7] = (value and 0xff).toByte()
}
fun ByteArray.getLong(index: Int): Long {
return (
(this[index].toLong() shl 56) or
((this[index+1].toLong() and 0xff) shl 48) or
((this[index+2].toLong() and 0xff) shl 40) or
((this[index+3].toLong() and 0xff) shl 32) or
((this[index+4].toLong() and 0xff) shl 24) or
((this[index+5].toLong() and 0xff) shl 16) or
((this[index+6].toLong() and 0xff) shl 8) or
(this[index+7].toLong() and 0xff)
)
}
fun ByteArray.setDouble(index: Int, value: Double) = this.setLong(index, value.toRawBits())
fun ByteArray.getDouble(index: Int): Double = Double.fromBits(this.getLong(index))
fun ByteArray.setFloat(index: Int, value: Float) = this.setInt(index, value.toRawBits())
fun ByteArray.getFloat(index: Int): Float = Float.fromBits(this.getInt(index))
fun ByteArray.getString(index: Int): Pair<String, Int> {
val length = this.getInt(index)
val str = this.copyOfRange(index + 4, index + 4 + length).decodeToString()
return Pair(str, index + 4 + length)
}
fun ByteArray.setString(index: Int, value: String): Int {
val bytes = value.encodeToByteArray()
this.setInt(index, bytes.size)
bytes.copyInto(this, index + 4)
return index + bytes.size + 4
}

View File

@@ -0,0 +1,118 @@
package nl.astraeus.tba
class MutableByteArrayHandler(
initialSize: Int = 1024,
buffer: ByteArray = ByteArray(initialSize)
) : ByteArrayHandler(initialSize, buffer) {
operator fun set(index: Int, value: Byte) {
buffer[index] = value
}
fun setShort(index: Int, value: Short) {
buffer[index] = (value.toInt() shr 8).toByte()
buffer[index + 1] = value.toByte()
}
fun setInt(index: Int, value: Int) {
buffer[index] = ((value shr 24) and 0xff).toByte()
buffer[index + 1] = ((value shr 16) and 0xff).toByte()
buffer[index + 2] = ((value shr 8) and 0xff).toByte()
buffer[index + 3] = (value and 0xff).toByte()
}
fun setLong(index: Int, value: Long) {
buffer[index] = ((value shr 56) and 0xff).toByte()
buffer[index + 1] = ((value shr 48) and 0xff).toByte()
buffer[index + 2] = ((value shr 40) and 0xff).toByte()
buffer[index + 3] = ((value shr 32) and 0xff).toByte()
buffer[index + 4] = ((value shr 24) and 0xff).toByte()
buffer[index + 5] = ((value shr 16) and 0xff).toByte()
buffer[index + 6] = ((value shr 8) and 0xff).toByte()
buffer[index + 7] = (value and 0xff).toByte()
}
fun setFloat(index: Int, value: Float) = buffer.setInt(index, value.toRawBits())
fun setDouble(index: Int, value: Double) = buffer.setLong(index, value.toRawBits())
fun setString(index: Int, value: String, maxLength: Int): Int {
val bytes = value.encodeToByteArray()
if (bytes.size > maxLength) {
throw IllegalArgumentException("String is too long")
}
this.setShort(index, bytes.size.toShort())
bytes.copyInto(buffer, index + 2)
return index + bytes.size + 2
}
}
open class ByteArrayHandler(
size: Int = 1024,
var buffer: ByteArray = ByteArray(size),
val firstIndex: Int = 0,
val lastIndex: Int = buffer.size
) {
constructor(buffer: ByteArray, range: IntRange) : this(
buffer = buffer,
firstIndex = range.first,
lastIndex = range.last
)
operator fun get(index: Int): Byte {
return buffer[index]
}
fun getShort(index: Int): Short {
return ((buffer[index].toInt() shl 8) or (buffer[index + 1].toInt() and 0xff)).toShort()
}
fun getInt(index: Int): Int {
return (
(buffer[index].toInt() shl 24) or
((buffer[index+1].toInt() shl 16) and 0xff0000) or
((buffer[index+2].toInt() shl 8) and 0xff00) or
(buffer[index+3].toInt() and 0xff)
)
}
fun getLong(index: Int): Long {
return (
(buffer[index].toLong() shl 56) or
((buffer[index+1].toLong() and 0xff) shl 48) or
((buffer[index+2].toLong() and 0xff) shl 40) or
((buffer[index+3].toLong() and 0xff) shl 32) or
((buffer[index+4].toLong() and 0xff) shl 24) or
((buffer[index+5].toLong() and 0xff) shl 16) or
((buffer[index+6].toLong() and 0xff) shl 8) or
(buffer[index+7].toLong() and 0xff)
)
}
fun getFloat(index: Int): Float = Float.fromBits(buffer.getInt(index))
fun getDouble(index: Int): Double = Double.fromBits(buffer.getLong(index))
fun getString(index: Int): String {
val length = getShort(index)
val str = buffer.copyOfRange(index + 2, index + 2 + length).decodeToString()
return str
}
fun getClob(index: Int): String {
val length = getInt(index)
val str = buffer.copyOfRange(index + 4, index + 4 + length).decodeToString()
return str
}
fun slice(range: IntRange): ByteArrayHandler {
return ByteArrayHandler(
buffer,
range
)
}
fun asMutable(): MutableByteArrayHandler {
return MutableByteArrayHandler(buffer = buffer.copyOfRange(firstIndex, lastIndex))
}
}

View File

@@ -0,0 +1,123 @@
package nl.astraeus.tba
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class ByteProperty(
val name: String
) : ReadWriteProperty<TypedByteArray, Byte> {
var index: Int? = null
private fun getIndex(thisRef: TypedByteArray): Int {
if (index == null) {
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
}
return index!!
}
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Byte {
return thisRef.data[getIndex(thisRef)]
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Byte) {
thisRef.data [getIndex(thisRef)] = value
}
}
class ShortProperty(
val name: String
) : ReadWriteProperty<TypedByteArray, Short> {
var index: Int? = null
private fun getIndex(thisRef: TypedByteArray): Int {
if (index == null) {
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
}
return index!!
}
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Short {
return thisRef.data.getShort(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Short) {
thisRef.data.setShort(getIndex(thisRef), value)
}
}
class IntProperty(
val name: String
) : ReadWriteProperty<TypedByteArray, Int> {
var index: Int? = null
private fun getIndex(thisRef: TypedByteArray): Int {
if (index == null) {
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
}
return index!!
}
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Int {
return thisRef.data.getInt(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Int) {
thisRef.data.setInt(getIndex(thisRef), value)
}
}
class LongProperty(
val name: String
) : ReadWriteProperty<TypedByteArray, Long> {
var index: Int? = null
private fun getIndex(thisRef: TypedByteArray): Int {
if (index == null) {
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
}
return index!!
}
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Long {
return thisRef.data.getLong(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Long) {
thisRef.data.setLong(getIndex(thisRef), value)
}
}
class StringProperty(
val name: String
) : ReadWriteProperty<TypedByteArray, String> {
var index: Int? = null
var maxLength: Int? = null
private fun getIndex(thisRef: TypedByteArray): Int {
if (index == null) {
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
}
return index!!
}
private fun getMaxLength(thisRef: TypedByteArray): Int {
if (maxLength == null) {
maxLength = thisRef.typeMap[name]!!.size - 2
}
return maxLength!!
}
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): String {
return thisRef.data.getString(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: String) {
thisRef.data.setString(getIndex(thisRef), value, getMaxLength(thisRef))
}
}
fun byte(propertyName: String) = ByteProperty(propertyName)
fun short(propertyName: String) = ShortProperty(propertyName)
fun int(propertyName: String) = IntProperty(propertyName)
fun long(propertyName: String) = LongProperty(propertyName)
fun string(propertyName: String) = StringProperty(propertyName)

View File

@@ -0,0 +1,21 @@
package nl.astraeus.tba
open class TypedByteArray(
val definition: ByteArrayDefinition,
var data: MutableByteArrayHandler = MutableByteArrayHandler(definition.size),
) {
val typeMap = mutableMapOf<String, Type>()
val indexMap = mutableMapOf<String, Int>()
init {
var index = 0
for (type in definition.types) {
check (!typeMap.containsKey(type.name)) { "Duplicate type name ${type.name}" }
check (!indexMap.containsKey(type.name)) { "Duplicate type name ${type.name}" }
typeMap[type.name] = type
indexMap[type.name] = index
index += type.size
}
}
}

View File

@@ -0,0 +1,81 @@
package nl.astraeus.tba
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class ExtendedByteArrayDefinition {
open class MessageByteArray(
vararg types: Type
) : TypedByteArray(
BaseByteArrayDefinition(
Type("messageType", DataType.BYTE),
Type("messageLength", DataType.SHORT),
Type("message", DataType.STRING, 1024),
*types,
)
) {
var messageType by byte("messageType")
var messageLength by short("messageLength")
var message by string("message")
}
class MessageBlaat() : MessageByteArray(
Type("name", DataType.STRING, 10),
Type("age", DataType.BYTE),
Type("length", DataType.SHORT),
) {
var name by string("name")
var age by byte("age")
var length by short("length")
constructor(
type: Byte,
messageLength: Short,
message: String,
name: String,
age: Byte,
length: Short
) : this() {
this.messageType = type
this.messageLength = messageLength
this.message = message
this.name = name
this.age = age
this.length = length
}
constructor(data: ByteArray) : this() {
this.data = MutableByteArrayHandler(buffer = data)
}
}
@Test
fun testSimpleTypedByteArray() {
val exception = assertFailsWith(IllegalArgumentException::class) {
MessageBlaat(
12,
123,
"This is the message!",
"Test is a very long name, now what?",
42,
180
)
}
val msg = MessageBlaat(
12,
123,
"This is the message!",
"12345678",
42,
180
)
assertEquals("12345678", msg.name)
assertEquals(42.toByte(), msg.age)
assertEquals(180.toShort(), msg.length)
}
}

View File

@@ -0,0 +1,53 @@
package nl.astraeus.tba
import kotlin.test.Test
import kotlin.test.assertEquals
class TypedByteArrayTest {
class Person() : TypedByteArray(
BaseByteArrayDefinition(
Type("name", DataType.STRING, 102),
Type("age", DataType.BYTE),
Type("length", DataType.SHORT),
)
) {
var name by string("name")
var age by byte("age")
var length by short("length")
constructor(name: String, age: Byte, length: Short): this() {
this.name = name
this.age = age
this.length = length
}
constructor(data: ByteArray): this() {
this.data = MutableByteArrayHandler(buffer = data)
}
}
@Test
fun testSimpleTypedByteArray() {
val person = Person("Test", 42, 180)
assertEquals(4, person.data.buffer[1])
assertEquals(42, person.data.buffer[102])
assertEquals("Test", person.name)
assertEquals(42.toByte(), person.age)
assertEquals(180.toShort(), person.length)
}
@Test
fun testToByteArrayAndBack() {
val person = Person("Test", 42, 180)
val bytes: ByteArray = person.data.buffer
val person2 = Person(bytes)
assertEquals("Test", person2.name)
assertEquals(42.toByte(), person2.age)
assertEquals(180.toShort(), person2.length)
}
}