Skip to content

Commit

Permalink
Fix usage of BigInteger in DiscordBitSet.value (#864)
Browse files Browse the repository at this point in the history
DiscordBitSet can only represent positive numbers, so the BigInteger
used for getting a decimal representation can be constructed using the
sign-magnitude representation instead of the two's-complement binary
representation.

This fixes two bugs:

 * DiscordBitSets with an empty data array couldn't be converted to a
   BigInteger because the constructor taking the two's-complement binary
   representation throws a NumberFormatException if the given array is
   empty.
   This bug was reported by @Tmpod:
   https://discord.com/channels/556525343595298817/1147254164469063773

 * DiscordBitSets with a negative Long at the last position in their
   data array would be misinterpreted as negative numbers in the
   conversion to BigIntegers because the two's-complement binary
   representation was used.
   Example where this bug could be observed:
   // printed -9223372036854775808, should be 9223372036854775808
   println(DiscordBitSet(1L shl 63).value)
  • Loading branch information
lukellmann authored Sep 2, 2023
1 parent fc1d70f commit 6749400
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 1 deletion.
27 changes: 27 additions & 0 deletions common/src/commonTest/kotlin/BitSetTests.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dev.kord.common

import kotlin.js.JsName
import kotlin.random.Random
import kotlin.random.nextLong
import kotlin.test.*

class BitSetTests {
Expand Down Expand Up @@ -99,4 +101,29 @@ class BitSetTests {
DiscordBitSet(0b1011, 0b111001, 0b110).binary,
)
}

@Test
fun value_works_for_DiscordBitSet_with_empty_data_array() {
val bits = DiscordBitSet(data = LongArray(size = 0))
assertEquals("0", bits.value)
}

@Test
fun value_works_for_all_single_bit_Longs() {
for (shift in 0..<Long.SIZE_BITS) {
val value = 1L shl shift
val bits = DiscordBitSet(value)
assertEquals(value.toULong().toString(), bits.value)
}
}

@Test
fun value_is_never_negative() {
for (size in 1..10) {
val data = LongArray(size)
data[size - 1] = Random.nextLong(Long.MIN_VALUE..-1)
val bits = DiscordBitSet(data)
assertTrue(bits.value.all { it in '0'..'9' })
}
}
}
2 changes: 1 addition & 1 deletion common/src/jvmMain/kotlin/DiscordBitSetJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal actual fun formatIntegerFromLittleEndianLongArray(data: LongArray): Str
// need to convert from little-endian data to big-endian expected by BigInteger
val buffer = ByteBuffer.allocate(data.size * Long.SIZE_BYTES)
buffer.asLongBuffer().put(data.reversedArray())
return BigInteger(buffer.array()).toString()
return BigInteger(/* signum = */ 1, /* magnitude = */ buffer.array()).toString()
}

internal actual fun parseIntegerToBigEndianByteArray(value: String): ByteArray = BigInteger(value).toByteArray()

0 comments on commit 6749400

Please sign in to comment.