diff --git a/library/bits/build.gradle.kts b/library/bits/build.gradle.kts index 15b66c6..d889045 100644 --- a/library/bits/build.gradle.kts +++ b/library/bits/build.gradle.kts @@ -18,5 +18,13 @@ plugins { } kmpConfiguration { - configureShared(java9ModuleName = "org.kotlincrypto.bitops.bits", publish = true) {} + configureShared(java9ModuleName = "org.kotlincrypto.bitops.bits", publish = true) { + jvm { + sourceSetTest { + dependencies { + implementation(project(":library:endian")) + } + } + } + } } diff --git a/library/bits/src/commonMain/kotlin/org/kotlincrypto/bitops/bits/Counter.kt b/library/bits/src/commonMain/kotlin/org/kotlincrypto/bitops/bits/Counter.kt index c53c359..2beac90 100644 --- a/library/bits/src/commonMain/kotlin/org/kotlincrypto/bitops/bits/Counter.kt +++ b/library/bits/src/commonMain/kotlin/org/kotlincrypto/bitops/bits/Counter.kt @@ -278,7 +278,7 @@ public sealed class Counter private constructor() { public override fun asBits(): Final { if (isBits) return this - return Final(lo shl 3, (hi shl 3) or (lo ushr 29), isBits = true) + return Final(lo shl 3, (hi shl 3) or (lo ushr 61), isBits = true) } } diff --git a/library/bits/src/commonTest/kotlin/org/kotlincrypto/bitops/bits/CounterUnitTest.kt b/library/bits/src/commonTest/kotlin/org/kotlincrypto/bitops/bits/CounterUnitTest.kt index a104e37..3a71585 100644 --- a/library/bits/src/commonTest/kotlin/org/kotlincrypto/bitops/bits/CounterUnitTest.kt +++ b/library/bits/src/commonTest/kotlin/org/kotlincrypto/bitops/bits/CounterUnitTest.kt @@ -209,21 +209,4 @@ class CounterUnitTest { val actual = ((bits.hi.toLong() and 0xffffffff) shl 32) or (bits.lo.toLong() and 0xffffffff) assertEquals(expected, actual) } - - @Test - fun givenBit64Final_whenAsBits_thenConvertsAsExpected() { - val number = 5551889119L - val final = Counter.Bit64.Final(lo = number, hi = 1) - val bits = final.asBits() - assertNotEquals(final, bits) - - // Should return same instance (already bits) - assertEquals(bits, bits.asBits()) - - var expected = Long.MAX_VALUE - expected += number + 1 - expected *= Byte.SIZE_BITS - val actual = ((bits.hi and 0xffffffff) shl 32) or (bits.lo and 0xffffffff) - assertEquals(expected, actual) - } } diff --git a/library/bits/src/jvmTest/kotlin/org/kotlincrypto/bitops/bits/CounterJvmUnitTest.kt b/library/bits/src/jvmTest/kotlin/org/kotlincrypto/bitops/bits/CounterJvmUnitTest.kt new file mode 100644 index 0000000..07a3bbc --- /dev/null +++ b/library/bits/src/jvmTest/kotlin/org/kotlincrypto/bitops/bits/CounterJvmUnitTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 Matthew Nelson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +package org.kotlincrypto.bitops.bits + +import org.kotlincrypto.bitops.endian.Endian.Big.beLongAt +import java.math.BigInteger +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * Uses Java's [BigInteger] to verify [Counter.Bit64.Final.asBits] functions as expected. + * */ +class CounterJvmUnitTest { + + @Test + fun givenBit64Final_whenAsMaxBits_thenConvertsAsExpected() { + val max = BigInteger("2").pow(128) + val maxBytes = max.divide(BigInteger("8")) + val maxBytesMinus1 = maxBytes.minus(BigInteger.ONE) + val expectedBits = maxBytesMinus1.times(BigInteger("8")) + + val bMaxBytesMinus1 = maxBytesMinus1.toByteArray() + val bExpectedBits = expectedBits.toByteArray() + + val bits = Counter.Bit64.Final( + lo = bMaxBytesMinus1.beLongAt(8), + hi = bMaxBytesMinus1.beLongAt(0), + ).asBits() + + // BigInteger.toByteArray returns 17 bytes, prefixed with + // a leading 0 which is dropped. + val expectedLo = bExpectedBits.beLongAt(9) + val expectedHi = bExpectedBits.beLongAt(1) + + assertEquals(-8L, expectedLo) + assertEquals(-1L, expectedHi) + + assertEquals(expectedLo, bits.lo) + assertEquals(expectedHi, bits.hi) + } + + @Test + fun givenBit64Final_whenAsBits_thenConvertsAsExpected() { + val pow67 = BigInteger("2").pow(67).minus(BigInteger.ONE) + val expectedBits = pow67.times(BigInteger("8")) + + // BigInteger.toByteArray returns 9 bytes (most significant + // number stripped of leading zeros) + val bPow67 = pow67.toByteArray() + val bExpectedBits = expectedBits.toByteArray() + + val bits = Counter.Bit64.Final( + lo = bPow67.beLongAt(1), + hi = bPow67[0].toLong(), + ).asBits() + + val expectedLo = bExpectedBits.beLongAt(1) + val expectedHi = bExpectedBits[0].toLong() + + assertEquals(-8, expectedLo) + assertEquals(63, expectedHi) + + assertEquals(expectedLo, bits.lo) + assertEquals(expectedHi, bits.hi) + } + + @Test + fun givenBit64Final_whenAsBitsFromRandomLargeNumber_thenConvertsAsExpected() { + repeat(500) { + val number = BigInteger("2").pow(88).minus( + BigInteger( + Random.Default.nextLong(Long.MAX_VALUE - 500_000_000, Long.MAX_VALUE).toString() + ) + ) + val nBits = number.times(BigInteger("8")) + + val bNumber = number.toByteArray().to16Bytes() + val bNBits = nBits.toByteArray().to16Bytes() + + val bits = Counter.Bit64.Final( + lo = bNumber.beLongAt(8), + hi = bNumber.beLongAt(0), + ).asBits() + + val expectedLo = bNBits.beLongAt(8) + val expectedHi = bNBits.beLongAt(0) + + assertEquals(expectedLo, bits.lo) + assertEquals(expectedHi, bits.hi) + } + } + + private fun ByteArray.to16Bytes(): ByteArray { + require(this.size <= 16) { "array.size is greater than 16 bytes..." } + val b = ByteArray(16) + copyInto(b, destinationOffset = 16 - size) + return b + } +}