From 848c4022468016317f8d8615e810296e1c73ea48 Mon Sep 17 00:00:00 2001
From: DBotThePony <dbotthepony@yandex.ru>
Date: Sun, 11 Feb 2024 12:57:02 +0700
Subject: [PATCH] Fix varint writing

---
 build.gradle.kts                              |  6 ++
 gradle.properties                             |  3 +-
 .../kommons/io/InputStreamUtils.kt            | 65 ++++++++++++++++++-
 .../kommons/io/OutputStreamUtils.kt           | 58 +++++++++++++----
 .../dbotthepony/kommons/test/StreamTests.kt   | 38 +++++++++++
 5 files changed, 156 insertions(+), 14 deletions(-)
 create mode 100644 src/test/kotlin/ru/dbotthepony/kommons/test/StreamTests.kt

diff --git a/build.gradle.kts b/build.gradle.kts
index 1fe81a3..a3af6b5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -20,9 +20,15 @@ val projectGroup: String by project
 val projectVersion: String by project
 val specifyKotlinAsDependency: String by project
 val fastutilVersion: String by project
+val jupiterVersion: String by project
+
+tasks.test {
+	useJUnitPlatform()
+}
 
 dependencies {
 	testImplementation("org.jetbrains.kotlin:kotlin-test")
+	testImplementation("org.junit.jupiter:junit-jupiter:${jupiterVersion}")
 
 	implementation("it.unimi.dsi:fastutil:$fastutilVersion")
 }
diff --git a/gradle.properties b/gradle.properties
index 30fcabd..2c5bdab 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,8 +4,9 @@ kotlin.code.style=official
 specifyKotlinAsDependency=false
 
 projectGroup=ru.dbotthepony.kommons
-projectVersion=2.1.7
+projectVersion=2.1.8
 
 guavaDepVersion=33.0.0
 gsonDepVersion=2.8.9
 fastutilVersion=8.5.6
+jupiterVersion=5.9.2
diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/InputStreamUtils.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/InputStreamUtils.kt
index c232d21..b8de787 100644
--- a/src/main/kotlin/ru/dbotthepony/kommons/io/InputStreamUtils.kt
+++ b/src/main/kotlin/ru/dbotthepony/kommons/io/InputStreamUtils.kt
@@ -89,7 +89,7 @@ private fun readVarLongInfo(supplier: IntSupplier): VarLongReadResult {
 		throw EOFException()
 
 	while (true) {
-		result = (result shl 7) or (read.toLong() and 0x7F)
+		result = (result shl 7) or (read.toLong() and 0x7FL)
 
 		if (read and 0x80 == 0)
 			break
@@ -154,22 +154,85 @@ private fun VarLongReadResult.fromSignedVar(): VarLongReadResult {
 		return VarLongReadResult(-(this.value ushr 1) - 1, this.cells)
 }
 
+/**
+ * Reads unsigned variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.readVarLong() = readVarLong(::readUnsignedByte)
+
+/**
+ * Reads unsigned variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.readVarInt() = readVarInt(::readUnsignedByte)
+
+/**
+ * Reads unsigned variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.readVarLongInfo() = readVarLongInfo(::readUnsignedByte)
+
+/**
+ * Reads unsigned variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.readVarIntInfo() = readVarIntInfo(::readUnsignedByte)
+
+/**
+ * Reads unsigned variable length integer, with Big-endian byte order
+ */
 fun InputStream.readVarLong() = readVarLong(::read)
+
+/**
+ * Reads unsigned variable length integer, with Big-endian byte order
+ */
 fun InputStream.readVarInt() = readVarInt(::read)
+
+/**
+ * Reads unsigned variable length integer, with Big-endian byte order
+ */
 fun InputStream.readVarLongInfo() = readVarLongInfo(::read)
+
+/**
+ * Reads unsigned variable length integer, with Big-endian byte order
+ */
 fun InputStream.readVarIntInfo() = readVarIntInfo(::read)
 
+
+/**
+ * Reads signed variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.readSignedVarLong() = readVarLong(::readUnsignedByte).fromSignedVar()
+
+/**
+ * Reads signed variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.readSignedVarInt() = readVarInt(::readUnsignedByte).fromSignedVar()
+
+/**
+ * Reads signed variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.readSignedVarLongInfo() = readVarLongInfo(::readUnsignedByte).fromSignedVar()
+
+/**
+ * Reads signed variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.readSignedVarIntInfo() = readVarIntInfo(::readUnsignedByte).fromSignedVar()
+
+/**
+ * Reads signed variable length integer, with Big-endian byte order
+ */
 fun InputStream.readSignedVarLong() = readVarLong(::read).fromSignedVar()
+
+/**
+ * Reads signed variable length integer, with Big-endian byte order
+ */
 fun InputStream.readSignedVarInt() = readVarInt(::read).fromSignedVar()
+
+/**
+ * Reads signed variable length integer, with Big-endian byte order
+ */
 fun InputStream.readSignedVarLongInfo() = readVarLongInfo(::read).fromSignedVar()
+
+/**
+ * Reads signed variable length integer, with Big-endian byte order
+ */
 fun InputStream.readSignedVarIntInfo() = readVarIntInfo(::read).fromSignedVar()
 
 fun InputStream.readBinaryString(): String {
diff --git a/src/main/kotlin/ru/dbotthepony/kommons/io/OutputStreamUtils.kt b/src/main/kotlin/ru/dbotthepony/kommons/io/OutputStreamUtils.kt
index b7dbb14..1f5cf48 100644
--- a/src/main/kotlin/ru/dbotthepony/kommons/io/OutputStreamUtils.kt
+++ b/src/main/kotlin/ru/dbotthepony/kommons/io/OutputStreamUtils.kt
@@ -83,22 +83,25 @@ fun OutputStream.writeFloat(value: Float) = writeInt(value.toBits())
 fun OutputStream.writeDouble(value: Double) = writeLong(value.toBits())
 
 private fun writeVarLong(write: IntConsumer, value: Long): Int {
-	var value = value
-	var i = 0
+	var length = 9
 
-	do {
-		val toWrite = (value and 0x7F).toInt()
-		value = value ushr 7
+	while (length > 0) {
+		val octetsPresent = (value ushr (length * 7)) and 0x7FL
 
-		if (value == 0L)
-			write.accept(toWrite)
-		else
-			write.accept(toWrite or 0x80)
+		if (octetsPresent != 0L) {
+			break
+		} else {
+			length--
+		}
+	}
 
-		i++
-	} while (value != 0L)
+	for (octet in length downTo 1) {
+		write.accept(((value ushr (octet * 7)) and 0x7FL).toInt() or 0x80)
+	}
 
-	return i
+	write.accept((value and 0x7FL).toInt())
+
+	return length + 1
 }
 
 private fun writeVarInt(write: IntConsumer, value: Int): Int {
@@ -119,14 +122,45 @@ private fun Long.toSignedVar(): Long {
 		return ((-this - 1) shl 1) or 1L
 }
 
+/**
+ * Writes unsigned variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.writeVarLong(value: Long) = writeVarLong(::write, value)
+
+/**
+ * Writes unsigned variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.writeVarInt(value: Int) = writeVarInt(::write, value)
+
+/**
+ * Writes unsigned variable length integer, with Big-endian byte order
+ */
 fun OutputStream.writeVarLong(value: Long) = writeVarLong(::write, value)
+
+/**
+ * Writes unsigned variable length integer, with Big-endian byte order
+ */
 fun OutputStream.writeVarInt(value: Int) = writeVarInt(::write, value)
 
+
+/**
+ * Writes signed variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.writeSignedVarLong(value: Long) = writeVarLong(::write, value.toSignedVar())
+
+/**
+ * Writes signed variable length integer, with Big-endian byte order
+ */
 fun RandomAccessFile.writeSignedVarInt(value: Int) = writeVarInt(::write, value.toSignedVar())
+
+/**
+ * Writes signed variable length integer, with Big-endian byte order
+ */
 fun OutputStream.writeSignedVarLong(value: Long) = writeVarLong(::write, value.toSignedVar())
+
+/**
+ * Writes signed variable length integer, with Big-endian byte order
+ */
 fun OutputStream.writeSignedVarInt(value: Int) = writeVarInt(::write, value.toSignedVar())
 
 fun OutputStream.writeBinaryString(input: String) {
diff --git a/src/test/kotlin/ru/dbotthepony/kommons/test/StreamTests.kt b/src/test/kotlin/ru/dbotthepony/kommons/test/StreamTests.kt
new file mode 100644
index 0000000..cb7bccb
--- /dev/null
+++ b/src/test/kotlin/ru/dbotthepony/kommons/test/StreamTests.kt
@@ -0,0 +1,38 @@
+package ru.dbotthepony.kommons.test
+
+import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
+import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
+import org.junit.jupiter.api.Test
+import ru.dbotthepony.kommons.io.readVarInt
+import ru.dbotthepony.kommons.io.readVarIntInfo
+import ru.dbotthepony.kommons.io.writeVarInt
+import kotlin.test.assertEquals
+
+object StreamTests {
+	private fun readWrite(value: Int) {
+		val output = FastByteArrayOutputStream()
+		val input = FastByteArrayInputStream(output.array, 0, output.length)
+		val r = input.readVarIntInfo()
+		assertEquals(value, r.value)
+	}
+
+	@Test
+	fun varInts() {
+		readWrite(0)
+		readWrite(1)
+		readWrite(2)
+		readWrite(3)
+		readWrite(63)
+		readWrite(64)
+		readWrite(65)
+		readWrite(120)
+		readWrite(121)
+		readWrite(126)
+		readWrite(127)
+		readWrite(128)
+		readWrite(129)
+		readWrite(254)
+		readWrite(255)
+		readWrite(256)
+	}
+}
\ No newline at end of file