diff --git a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestBenchmark.kt b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestBenchmark.kt index 2e85c65..d6478fd 100644 --- a/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestBenchmark.kt +++ b/benchmarks/src/commonMain/kotlin/org/kotlincrypto/core/benchmarks/DigestBenchmark.kt @@ -17,7 +17,6 @@ package org.kotlincrypto.core.benchmarks import kotlinx.benchmark.* import org.kotlincrypto.core.digest.Digest -import org.kotlincrypto.core.digest.internal.DigestState import kotlin.random.Random @State(Scope.Benchmark) @@ -29,11 +28,11 @@ open class DigestBenchmark { private class TestDigest: Digest { constructor(): super("Benchmark", 32, 32) - private constructor(state: DigestState): super(state) - override fun resetDigest() {} - override fun copy(state: DigestState): Digest = TestDigest(state) - override fun compress(input: ByteArray, offset: Int) {} - override fun digest(bitLength: Long, bufferOffset: Int, buffer: ByteArray): ByteArray = ByteArray(0) + private constructor(state: State): super(state) + override fun resetProtected() {} + override fun copyProtected(state: State): Digest = TestDigest(state) + override fun compressProtected(input: ByteArray, offset: Int) {} + override fun digestProtected(buffer: ByteArray, offset: Int): ByteArray = ByteArray(0) } private val digest = TestDigest() diff --git a/gradle.properties b/gradle.properties index 3793969..9754aad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,10 +30,10 @@ POM_DEVELOPER_ID=KotlinCrypto POM_DEVELOPER_NAME=Kotlin Crypto POM_DEVELOPER_URL=https://github.com/KotlinCrypto/ -VERSION_NAME=0.5.6-SNAPSHOT +VERSION_NAME=1.0.0-SNAPSHOT # 0.1.0-alpha01 = 00 01 00 11 # 0.1.0-beta01 = 00 01 00 21 # 0.1.0-rc01 = 00 01 00 31 # 0.1.0 = 00 01 00 99 # 1.1.0 = 01 01 00 99 -VERSION_CODE=00050699 +VERSION_CODE=01000099 diff --git a/library/core/api/core.api b/library/core/api/core.api index 14d7148..eab9c5c 100644 --- a/library/core/api/core.api +++ b/library/core/api/core.api @@ -6,6 +6,46 @@ public abstract interface class org/kotlincrypto/core/Copyable { public abstract fun copy ()Ljava/lang/Object; } +public abstract class org/kotlincrypto/core/Counter { + public final fun equals (Ljava/lang/Object;)Z + public final fun hashCode ()I + protected abstract fun increment ()V + protected abstract fun reset ()V + public final fun toString ()Ljava/lang/String; +} + +public abstract class org/kotlincrypto/core/Counter$Bit32 : org/kotlincrypto/core/Counter { + public static final field Companion Lorg/kotlincrypto/core/Counter$Bit32$Companion; + public static final field MAX_INCREMENT I + public final field incrementBy I + public fun (I)V + public fun (III)V + public fun (Lorg/kotlincrypto/core/Counter$Bit32;)V + public final fun hi ()I + protected fun increment ()V + public final fun lo ()I + protected fun reset ()V +} + +public final class org/kotlincrypto/core/Counter$Bit32$Companion { +} + +public abstract class org/kotlincrypto/core/Counter$Bit64 : org/kotlincrypto/core/Counter { + public static final field Companion Lorg/kotlincrypto/core/Counter$Bit64$Companion; + public static final field MAX_INCREMENT J + public final field incrementBy J + public fun (J)V + public fun (JJJ)V + public fun (Lorg/kotlincrypto/core/Counter$Bit64;)V + public final fun hi ()J + protected fun increment ()V + public final fun lo ()J + protected fun reset ()V +} + +public final class org/kotlincrypto/core/Counter$Bit64$Companion { +} + public abstract interface annotation class org/kotlincrypto/core/ExperimentalKotlinCryptoApi : java/lang/annotation/Annotation { } diff --git a/library/core/api/core.klib.api b/library/core/api/core.klib.api index 2f8de61..d8d9135 100644 --- a/library/core/api/core.klib.api +++ b/library/core/api/core.klib.api @@ -31,3 +31,55 @@ abstract interface org.kotlincrypto.core/Updatable { // org.kotlincrypto.core/Up abstract fun update(kotlin/ByteArray) // org.kotlincrypto.core/Updatable.update|update(kotlin.ByteArray){}[0] abstract fun update(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core/Updatable.update|update(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] } + +sealed class org.kotlincrypto.core/Counter { // org.kotlincrypto.core/Counter|null[0] + abstract fun increment() // org.kotlincrypto.core/Counter.increment|increment(){}[0] + abstract fun reset() // org.kotlincrypto.core/Counter.reset|reset(){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.kotlincrypto.core/Counter.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.kotlincrypto.core/Counter.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.kotlincrypto.core/Counter.toString|toString(){}[0] + + abstract class Bit32 : org.kotlincrypto.core/Counter { // org.kotlincrypto.core/Counter.Bit32|null[0] + constructor (kotlin/Int) // org.kotlincrypto.core/Counter.Bit32.|(kotlin.Int){}[0] + constructor (kotlin/Int, kotlin/Int, kotlin/Int) // org.kotlincrypto.core/Counter.Bit32.|(kotlin.Int;kotlin.Int;kotlin.Int){}[0] + constructor (org.kotlincrypto.core/Counter.Bit32) // org.kotlincrypto.core/Counter.Bit32.|(org.kotlincrypto.core.Counter.Bit32){}[0] + + final val incrementBy // org.kotlincrypto.core/Counter.Bit32.incrementBy|{}incrementBy[0] + final fun (): kotlin/Int // org.kotlincrypto.core/Counter.Bit32.incrementBy.|(){}[0] + + final var hi // org.kotlincrypto.core/Counter.Bit32.hi|{}hi[0] + final fun (): kotlin/Int // org.kotlincrypto.core/Counter.Bit32.hi.|(){}[0] + final var lo // org.kotlincrypto.core/Counter.Bit32.lo|{}lo[0] + final fun (): kotlin/Int // org.kotlincrypto.core/Counter.Bit32.lo.|(){}[0] + + open fun increment() // org.kotlincrypto.core/Counter.Bit32.increment|increment(){}[0] + open fun reset() // org.kotlincrypto.core/Counter.Bit32.reset|reset(){}[0] + + final object Companion { // org.kotlincrypto.core/Counter.Bit32.Companion|null[0] + final const val MAX_INCREMENT // org.kotlincrypto.core/Counter.Bit32.Companion.MAX_INCREMENT|{}MAX_INCREMENT[0] + final fun (): kotlin/Int // org.kotlincrypto.core/Counter.Bit32.Companion.MAX_INCREMENT.|(){}[0] + } + } + + abstract class Bit64 : org.kotlincrypto.core/Counter { // org.kotlincrypto.core/Counter.Bit64|null[0] + constructor (kotlin/Long) // org.kotlincrypto.core/Counter.Bit64.|(kotlin.Long){}[0] + constructor (kotlin/Long, kotlin/Long, kotlin/Long) // org.kotlincrypto.core/Counter.Bit64.|(kotlin.Long;kotlin.Long;kotlin.Long){}[0] + constructor (org.kotlincrypto.core/Counter.Bit64) // org.kotlincrypto.core/Counter.Bit64.|(org.kotlincrypto.core.Counter.Bit64){}[0] + + final val incrementBy // org.kotlincrypto.core/Counter.Bit64.incrementBy|{}incrementBy[0] + final fun (): kotlin/Long // org.kotlincrypto.core/Counter.Bit64.incrementBy.|(){}[0] + + final var hi // org.kotlincrypto.core/Counter.Bit64.hi|{}hi[0] + final fun (): kotlin/Long // org.kotlincrypto.core/Counter.Bit64.hi.|(){}[0] + final var lo // org.kotlincrypto.core/Counter.Bit64.lo|{}lo[0] + final fun (): kotlin/Long // org.kotlincrypto.core/Counter.Bit64.lo.|(){}[0] + + open fun increment() // org.kotlincrypto.core/Counter.Bit64.increment|increment(){}[0] + open fun reset() // org.kotlincrypto.core/Counter.Bit64.reset|reset(){}[0] + + final object Companion { // org.kotlincrypto.core/Counter.Bit64.Companion|null[0] + final const val MAX_INCREMENT // org.kotlincrypto.core/Counter.Bit64.Companion.MAX_INCREMENT|{}MAX_INCREMENT[0] + final fun (): kotlin/Long // org.kotlincrypto.core/Counter.Bit64.Companion.MAX_INCREMENT.|(){}[0] + } + } +} diff --git a/library/core/src/commonMain/kotlin/org/kotlincrypto/core/Counter.kt b/library/core/src/commonMain/kotlin/org/kotlincrypto/core/Counter.kt new file mode 100644 index 0000000..7ae1c45 --- /dev/null +++ b/library/core/src/commonMain/kotlin/org/kotlincrypto/core/Counter.kt @@ -0,0 +1,212 @@ +/* + * 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.core + +import org.kotlincrypto.core.Counter.Bit32.Companion.MAX_INCREMENT +import org.kotlincrypto.core.Counter.Bit64.Companion.MAX_INCREMENT +import kotlin.jvm.JvmField +import kotlin.jvm.JvmName + +/** + * Utility for counting things. + * */ +public sealed class Counter private constructor() { + + /** + * A counter that utilizes 32-bit numbers providing a maximum count of 2^64. + * */ + public abstract class Bit32: Counter { + + public companion object { + + /** + * The maximum value for which [Bit32.incrementBy] can be set to. + * + * 1024 * 1024 >> 1048576 + * */ + public const val MAX_INCREMENT: Int = 1024 * 1024 // Never decrease, only increase. + } + + /** + * The value to increment things by + * */ + @JvmField + public val incrementBy: Int + + /** + * The least significant bits of the number + * */ + @get:JvmName("lo") + public var lo: Int + private set + + /** + * The most significant bits of the number + * */ + @get:JvmName("hi") + public var hi: Int + private set + + /** + * Creates a new [Bit32] counter initialized to [lo] and [hi] + * + * @throws [IllegalArgumentException] when: + * - [incrementBy] is less than or equal to 0 + * - [incrementBy] is greater than [MAX_INCREMENT] + * - [incrementBy] is not a factor of 8 + * - [lo] is not a factor of [incrementBy] + * */ + public constructor(lo: Int, hi: Int, incrementBy: Int): super() { + require(incrementBy > 0) { "incrementBy[$incrementBy] must be greater than 0" } + require(incrementBy <= MAX_INCREMENT) { "incrementBy[$incrementBy] must be less than or equal to $MAX_INCREMENT" } + require(incrementBy % 8 == 0) { "incrementBy[$incrementBy] must be a factor of 8" } + require(lo % incrementBy == 0) { "lo must be a factor of incrementBy[$incrementBy]" } + + this.incrementBy = incrementBy + this.lo = lo + this.hi = hi + } + + /** + * Creates a new [Bit32] counter initialized to 0, 0 + * + * @throws [IllegalArgumentException] when [incrementBy] is: + * - Less than or equal to 0 + * - Greater than [MAX_INCREMENT] + * - Not a factor of 8 + * */ + public constructor(incrementBy: Int): this(0, 0, incrementBy) + + /** + * Creates a clone of [other] + * */ + public constructor(other: Bit32): super() { + this.incrementBy = other.incrementBy + this.lo = other.lo + this.hi = other.hi + } + + protected override fun increment() { + lo += incrementBy + if (lo == 0) hi++ + } + + protected override fun reset() { + lo = 0 + hi = 0 + } + } + + /** + * A counter that utilizes 64-bit numbers providing a maximum count of 2^128. + * */ + public abstract class Bit64: Counter { + + public companion object { + + /** + * The maximum value for which [Bit64.incrementBy] can be set to. + * + * @see [Bit32.MAX_INCREMENT] + * */ + public const val MAX_INCREMENT: Long = Bit32.MAX_INCREMENT.toLong() + } + + /** + * The value to increment things by + * */ + @JvmField + public val incrementBy: Long + + /** + * The least significant bits of the number + * */ + @get:JvmName("lo") + public var lo: Long + private set + + /** + * The most significant bits of the number + * */ + @get:JvmName("hi") + public var hi: Long + private set + + /** + * Creates a new [Bit64] counter initialized to [lo] and [hi] + * + * @throws [IllegalArgumentException] when: + * - [incrementBy] is less than or equal to 0 + * - [incrementBy] is greater than [MAX_INCREMENT] + * - [incrementBy] is not a factor of 8 + * - [lo] is not a factor of [incrementBy] + * */ + public constructor(lo: Long, hi: Long, incrementBy: Long): super() { + require(incrementBy > 0L) { "incrementBy[$incrementBy] must be greater than 0" } + require(incrementBy <= MAX_INCREMENT) { "incrementBy[$incrementBy] must be less than or equal to $MAX_INCREMENT" } + require(incrementBy % 8 == 0L) { "incrementBy[$incrementBy] must be a factor of 8" } + require(lo % incrementBy == 0L) { "lo must be a factor of incrementBy[$incrementBy]" } + + this.incrementBy = incrementBy + this.lo = lo + this.hi = hi + } + + /** + * Creates a new [Bit64] counter initialized to 0, 0 + * + * @throws [IllegalArgumentException] when [incrementBy] is: + * - Less than or equal to 0 + * - Greater than [MAX_INCREMENT] + * - Not a factor of 8 + * */ + public constructor(incrementBy: Long): this(0, 0, incrementBy) + + /** + * Creates a clone of [other] + * */ + public constructor(other: Bit64): super() { + this.incrementBy = other.incrementBy + this.lo = other.lo + this.hi = other.hi + } + + protected override fun increment() { + lo += incrementBy + if (lo == 0L) hi++ + } + + protected override fun reset() { + lo = 0L + hi = 0L + } + } + + protected abstract fun increment() + protected abstract fun reset() + + private val code = Any() + + /** @suppress */ + public final override fun equals(other: Any?): Boolean = other is Counter && other.hashCode() == hashCode() + /** @suppress */ + public final override fun hashCode(): Int = 17 * 31 + code.hashCode() + /** @suppress */ + public final override fun toString(): String = when (this) { + is Bit32 -> "Bit32[lo=$lo, hi=$hi, incrementBy=$incrementBy]" + is Bit64 -> "Bit64[lo=$lo, hi=$hi, incrementBy=$incrementBy]" + }.let { value -> "Counter.$value@${hashCode()}" } +} diff --git a/library/core/src/commonTest/kotlin/org/kotlincrypto/core/CounterUnitTest.kt b/library/core/src/commonTest/kotlin/org/kotlincrypto/core/CounterUnitTest.kt new file mode 100644 index 0000000..fc10257 --- /dev/null +++ b/library/core/src/commonTest/kotlin/org/kotlincrypto/core/CounterUnitTest.kt @@ -0,0 +1,130 @@ +/* + * 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.core + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CounterUnitTest { + + @Test + fun givenIncrementBy_when0_thenThrowsException() { + assertFailsWith { + TestBit32Counter(0) + } + assertFailsWith { + TestBit64Counter(0) + } + } + + @Test + fun givenIncrementBy_whenExceedsMaximum_thenThrowsException() { + assertFailsWith { + TestBit32Counter(Counter.Bit32.MAX_INCREMENT + 8) + } + assertFailsWith { + TestBit64Counter(Counter.Bit64.MAX_INCREMENT + 8) + } + } + + @Test + fun givenIncrementBy_whenNotFactory8_thenThrowsException() { + assertFailsWith { + TestBit32Counter(9) + } + assertFailsWith { + TestBit64Counter(9) + } + } + + @Test + fun givenLo_whenNotFactoryIncrementBy_thenThrowsException() { + // Would throw if 24 was not a factor of 8... + TestBit32Counter(24) + TestBit64Counter(24) + + assertFailsWith { + TestBit32Counter(8, 0, 24) + } + assertFailsWith { + TestBit64Counter(16, 0, 24) + } + } + + @Test + fun givenBit32_whenReset_thenIsZero() { + val c = TestBit32Counter(8, 8, 8) + assertEquals(8, c.lo) + assertEquals(8, c.hi) + assertEquals(8, c.incrementBy) + c.reset() + assertEquals(0, c.lo) + assertEquals(0, c.hi) + } + + @Test + fun givenBit64_whenReset_thenIsZero() { + val c = TestBit64Counter(8, 8, 8) + assertEquals(8, c.lo) + assertEquals(8, c.hi) + assertEquals(8, c.incrementBy) + c.reset() + assertEquals(0, c.lo) + assertEquals(0, c.hi) + } + + @Test + fun givenBit32_whenIncrement_thenIncrements() { + var c = TestBit32Counter(-16, 0, 8) + c.increment() + assertEquals(-8, c.lo) + assertEquals(0, c.hi) + c.increment() + assertEquals(0, c.lo) + assertEquals(1, c.hi) + + c = TestBit32Counter(Int.MAX_VALUE - 7, 0, 8) + c.increment() + assertEquals(Int.MIN_VALUE, c.lo) + assertEquals(0, c.hi) + } + + @Test + fun givenBit64_whenIncrement_thenIncrements() { + var c = TestBit64Counter(-16, 0, 8) + c.increment() + assertEquals(-8, c.lo) + assertEquals(0, c.hi) + c.increment() + assertEquals(0, c.lo) + assertEquals(1, c.hi) + + c = TestBit64Counter(Long.MAX_VALUE - 7, 0, 8) + c.increment() + assertEquals(Long.MIN_VALUE, c.lo) + assertEquals(0, c.hi) + } + + @Test + fun givenBit32_whenClone_thenCopiesValues() { + val expected = TestBit32Counter(8, 8, 8) + val actual = TestBit32Counter(expected) + assertEquals(expected.incrementBy, actual.incrementBy) + assertEquals(expected.lo, actual.lo) + assertEquals(expected.hi, actual.hi) + } +} diff --git a/library/core/src/commonTest/kotlin/org/kotlincrypto/core/TestBit32Counter.kt b/library/core/src/commonTest/kotlin/org/kotlincrypto/core/TestBit32Counter.kt new file mode 100644 index 0000000..3105245 --- /dev/null +++ b/library/core/src/commonTest/kotlin/org/kotlincrypto/core/TestBit32Counter.kt @@ -0,0 +1,34 @@ +/* + * 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.core + +class TestBit32Counter: Counter.Bit32 { + constructor(lo: Int, hi: Int, incrementBy: Int): super(lo, hi, incrementBy) + constructor(incrementBy: Int): super(incrementBy) + constructor(other: TestBit32Counter): super(other) + + public override fun increment() { super.increment() } + public override fun reset() { super.reset() } +} + +class TestBit64Counter: Counter.Bit64 { + constructor(lo: Long, hi: Long, incrementBy: Long): super(lo, hi, incrementBy) + constructor(incrementBy: Long): super(incrementBy) + constructor(other: TestBit64Counter): super(other) + + public override fun increment() { super.increment() } + public override fun reset() { super.reset() } +} diff --git a/library/digest/api/digest.api b/library/digest/api/digest.api index 8c30a9a..18ba22c 100644 --- a/library/digest/api/digest.api +++ b/library/digest/api/digest.api @@ -1,19 +1,18 @@ public abstract class org/kotlincrypto/core/digest/Digest : java/security/MessageDigest, java/lang/Cloneable, org/kotlincrypto/core/Algorithm, org/kotlincrypto/core/Copyable, org/kotlincrypto/core/Resettable, org/kotlincrypto/core/Updatable { protected fun (Ljava/lang/String;II)V - protected fun (Lorg/kotlincrypto/core/digest/internal/DigestState;)V + protected fun (Lorg/kotlincrypto/core/digest/Digest$State;)V public final fun algorithm ()Ljava/lang/String; public final fun blockSize ()I public final fun clone ()Ljava/lang/Object; - protected abstract fun compress ([BI)V - protected final fun compressions ()J + protected abstract fun compressProtected ([BI)V public synthetic fun copy ()Ljava/lang/Object; public final fun copy ()Lorg/kotlincrypto/core/digest/Digest; - protected abstract fun copy (Lorg/kotlincrypto/core/digest/internal/DigestState;)Lorg/kotlincrypto/core/digest/Digest; + protected abstract fun copyProtected (Lorg/kotlincrypto/core/digest/Digest$State;)Lorg/kotlincrypto/core/digest/Digest; public final fun digest ()[B - protected abstract fun digest (JI[B)[B public final fun digest ([B)[B public final fun digest ([BII)I public final fun digestLength ()I + protected abstract fun digestProtected ([BI)[B protected final fun engineDigest ()[B protected final fun engineDigest ([BII)I protected final fun engineGetDigestLength ()I @@ -24,16 +23,18 @@ public abstract class org/kotlincrypto/core/digest/Digest : java/security/Messag public final fun equals (Ljava/lang/Object;)Z public final fun hashCode ()I public final fun reset ()V - protected abstract fun resetDigest ()V + protected abstract fun resetProtected ()V public final fun toString ()Ljava/lang/String; public final fun update (B)V public final fun update ([B)V public final fun update ([BII)V - protected fun updateDigest (B)V - protected fun updateDigest ([BII)V + protected fun updateProtected (B)V + protected fun updateProtected ([BII)V +} + +protected abstract class org/kotlincrypto/core/digest/Digest$State { } public abstract class org/kotlincrypto/core/digest/internal/DigestState { - public synthetic fun (Ljava/lang/String;IIIILkotlin/jvm/internal/DefaultConstructorMarker;)V } diff --git a/library/digest/api/digest.klib.api b/library/digest/api/digest.klib.api index 81acfd1..797e299 100644 --- a/library/digest/api/digest.klib.api +++ b/library/digest/api/digest.klib.api @@ -8,15 +8,14 @@ // Library unique name: abstract class org.kotlincrypto.core.digest/Digest : org.kotlincrypto.core/Algorithm, org.kotlincrypto.core/Copyable, org.kotlincrypto.core/Resettable, org.kotlincrypto.core/Updatable { // org.kotlincrypto.core.digest/Digest|null[0] constructor (kotlin/String, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.digest/Digest.|(kotlin.String;kotlin.Int;kotlin.Int){}[0] - constructor (org.kotlincrypto.core.digest.internal/DigestState) // org.kotlincrypto.core.digest/Digest.|(org.kotlincrypto.core.digest.internal.DigestState){}[0] + constructor (org.kotlincrypto.core.digest/Digest.State) // org.kotlincrypto.core.digest/Digest.|(org.kotlincrypto.core.digest.Digest.State){}[0] - abstract fun compress(kotlin/ByteArray, kotlin/Int) // org.kotlincrypto.core.digest/Digest.compress|compress(kotlin.ByteArray;kotlin.Int){}[0] - abstract fun copy(org.kotlincrypto.core.digest.internal/DigestState): org.kotlincrypto.core.digest/Digest // org.kotlincrypto.core.digest/Digest.copy|copy(org.kotlincrypto.core.digest.internal.DigestState){}[0] - abstract fun digest(kotlin/Long, kotlin/Int, kotlin/ByteArray): kotlin/ByteArray // org.kotlincrypto.core.digest/Digest.digest|digest(kotlin.Long;kotlin.Int;kotlin.ByteArray){}[0] - abstract fun resetDigest() // org.kotlincrypto.core.digest/Digest.resetDigest|resetDigest(){}[0] + abstract fun compressProtected(kotlin/ByteArray, kotlin/Int) // org.kotlincrypto.core.digest/Digest.compressProtected|compressProtected(kotlin.ByteArray;kotlin.Int){}[0] + abstract fun copyProtected(org.kotlincrypto.core.digest/Digest.State): org.kotlincrypto.core.digest/Digest // org.kotlincrypto.core.digest/Digest.copyProtected|copyProtected(org.kotlincrypto.core.digest.Digest.State){}[0] + abstract fun digestProtected(kotlin/ByteArray, kotlin/Int): kotlin/ByteArray // org.kotlincrypto.core.digest/Digest.digestProtected|digestProtected(kotlin.ByteArray;kotlin.Int){}[0] + abstract fun resetProtected() // org.kotlincrypto.core.digest/Digest.resetProtected|resetProtected(){}[0] final fun algorithm(): kotlin/String // org.kotlincrypto.core.digest/Digest.algorithm|algorithm(){}[0] final fun blockSize(): kotlin/Int // org.kotlincrypto.core.digest/Digest.blockSize|blockSize(){}[0] - final fun compressions(): kotlin/Long // org.kotlincrypto.core.digest/Digest.compressions|compressions(){}[0] final fun copy(): org.kotlincrypto.core.digest/Digest // org.kotlincrypto.core.digest/Digest.copy|copy(){}[0] final fun digest(): kotlin/ByteArray // org.kotlincrypto.core.digest/Digest.digest|digest(){}[0] final fun digest(kotlin/ByteArray): kotlin/ByteArray // org.kotlincrypto.core.digest/Digest.digest|digest(kotlin.ByteArray){}[0] @@ -25,13 +24,17 @@ abstract class org.kotlincrypto.core.digest/Digest : org.kotlincrypto.core/Algor final fun hashCode(): kotlin/Int // org.kotlincrypto.core.digest/Digest.hashCode|hashCode(){}[0] final fun reset() // org.kotlincrypto.core.digest/Digest.reset|reset(){}[0] final fun toString(): kotlin/String // org.kotlincrypto.core.digest/Digest.toString|toString(){}[0] + final fun update(kotlin/Byte) // org.kotlincrypto.core.digest/Digest.update|update(kotlin.Byte){}[0] final fun update(kotlin/ByteArray) // org.kotlincrypto.core.digest/Digest.update|update(kotlin.ByteArray){}[0] final fun update(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.digest/Digest.update|update(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - open fun update(kotlin/Byte) // org.kotlincrypto.core.digest/Digest.update|update(kotlin.Byte){}[0] - open fun updateDigest(kotlin/Byte) // org.kotlincrypto.core.digest/Digest.updateDigest|updateDigest(kotlin.Byte){}[0] - open fun updateDigest(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.digest/Digest.updateDigest|updateDigest(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + open fun updateProtected(kotlin/Byte) // org.kotlincrypto.core.digest/Digest.updateProtected|updateProtected(kotlin.Byte){}[0] + open fun updateProtected(kotlin/ByteArray, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.digest/Digest.updateProtected|updateProtected(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + + sealed class State { // org.kotlincrypto.core.digest/Digest.State|null[0] + constructor () // org.kotlincrypto.core.digest/Digest.State.|(){}[0] + } } sealed class org.kotlincrypto.core.digest.internal/DigestState { // org.kotlincrypto.core.digest.internal/DigestState|null[0] - constructor (kotlin/String, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int) // org.kotlincrypto.core.digest.internal/DigestState.|(kotlin.String;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] + constructor () // org.kotlincrypto.core.digest.internal/DigestState.|(){}[0] } diff --git a/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/Digest.kt b/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/Digest.kt index 89bced4..4956243 100644 --- a/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/Digest.kt +++ b/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/Digest.kt @@ -18,7 +18,6 @@ package org.kotlincrypto.core.digest import org.kotlincrypto.core.* -import org.kotlincrypto.core.digest.internal.DigestState /** * Core abstraction for Message Digest implementations. @@ -53,7 +52,7 @@ public expect abstract class Digest: Algorithm, Copyable, Resettable, Up * instance. * * Implementors of [Digest] should have a private secondary constructor - * that is utilized by its [copy] implementation. + * that is utilized by its [copyProtected] implementation. * * e.g. * @@ -62,18 +61,14 @@ public expect abstract class Digest: Algorithm, Copyable, Resettable, Up * public constructor(): super("SHA-256", 64, 32) { * // Initialize... * } - * private constructor(thiz: SHA256, state: DigestState): super(state) { + * private constructor(thiz: SHA256, state: State): super(state) { * // Copy implementation details... * } - * protected override fun copy(state: DigestState): Digest = SHA256(this, state) + * protected override fun copyProtected(state: State): Digest = SHA256(this, state) * // ... * } - * - * @see [DigestState] - * @throws [IllegalStateException] If [DigestState] has already been used to instantiate - * another instance of [Digest] * */ - protected constructor(state: DigestState) + protected constructor(state: State) /** * The number of byte blocks (in factors of 8) that the implementation @@ -118,55 +113,46 @@ public expect abstract class Digest: Algorithm, Copyable, Resettable, Up public final override fun copy(): Digest /** - * The number of compressions this [Digest] has completed. Backing - * variable is updated **after** each [compress] invocation, and - * subsequently set to `0` upon [reset] invocation. + * Used as a holder for copying digest internals. * */ - protected fun compressions(): Long + protected sealed class State /** - * Called by the public [copy] function which produces the [DigestState] + * Called by the public [copy] function which produces the [State] * needed to create a wholly new instance. - * - * **NOTE:** [DigestState] can only be consumed once and should **NOT** - * be held on to. Attempting to instantiate multiple [Digest] instances - * with a single [DigestState] will raise an [IllegalStateException]. * */ - protected abstract fun copy(state: DigestState): Digest + protected abstract fun copyProtected(state: State): Digest /** * Called whenever a full [blockSize] worth of bytes are available for processing, * starting at index [offset] for the provided [input]. Implementations **must not** * alter [input]. * */ - protected abstract fun compress(input: ByteArray, offset: Int) + protected abstract fun compressProtected(input: ByteArray, offset: Int) /** * Called to complete the computation, providing any input that may be * buffered awaiting processing. * - * @param [bitLength] The number of bits that have been processed, including - * those remaining in the [buffer] - * @param [bufferOffset] The index at which the next input would be placed in - * the [buffer] * @param [buffer] Unprocessed input + * @param [offset] The index at which the next input would be placed in the [buffer] * */ - protected abstract fun digest(bitLength: Long, bufferOffset: Int, buffer: ByteArray): ByteArray + protected abstract fun digestProtected(buffer: ByteArray, offset: Int): ByteArray /** * Optional override for implementations to intercept cleansed input before * being processed by the [Digest] abstraction. * */ - protected open fun updateDigest(input: Byte) + protected open fun updateProtected(input: Byte) /** * Optional override for implementations to intercept cleansed input before * being processed by the [Digest] abstraction. Parameters passed to this * function are always valid and have been checked for appropriateness. * */ - protected open fun updateDigest(input: ByteArray, offset: Int, len: Int) + protected open fun updateProtected(input: ByteArray, offset: Int, len: Int) - protected abstract fun resetDigest() + protected abstract fun resetProtected() /** @suppress */ public final override fun equals(other: Any?): Boolean diff --git a/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-Buffer.kt b/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-Buffer.kt index 4df983e..263dfaf 100644 --- a/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-Buffer.kt +++ b/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-Buffer.kt @@ -17,60 +17,16 @@ package org.kotlincrypto.core.digest.internal -import kotlin.concurrent.Volatile import kotlin.jvm.JvmInline import kotlin.jvm.JvmSynthetic @JvmInline internal value class Buffer private constructor(internal val value: ByteArray) { - internal fun toState( - algorithm: String, - digestLength: Int, - bufOffs: Int, - compressCount: Int, - compressCountMultiplier: Int, - ): DigestState = State( - algorithm, - digestLength, - bufOffs, - compressCount, - compressCountMultiplier, - this, - ) - - private class State( - algorithm: String, - digestLength: Int, - bufOffs: Int, - compressCount: Int, - compressCountMultiplier: Int, - buf: Buffer, - ): DigestState( - algorithm, - digestLength, - bufOffs, - compressCount, - compressCountMultiplier, - ) { - @Volatile - var buf: Buffer? = Buffer(buf.value.copyOf()) - } + internal fun copy(): Buffer = Buffer(value.copyOf()) internal companion object { - @JvmSynthetic - @Throws(IllegalStateException::class) - internal fun DigestState.buf(): Buffer { - val state = this as State - val buf = state.buf - state.buf = null - check(buf != null) { - "DigestState cannot be consumed more than once. Call copy again." - } - return buf - } - @JvmSynthetic @Throws(IllegalArgumentException::class) internal fun initialize( @@ -91,14 +47,16 @@ internal value class Buffer private constructor(internal val value: ByteArray) { internal inline fun Buffer.commonUpdate( input: Byte, bufOffsPlusPlus: Int, - doCompression: (buf: ByteArray, offset: Int) -> Unit, + bufOffsSet: (zero: Int) -> Unit, + compressProtected: (buf: ByteArray, offset: Int) -> Unit, ) { val buf = value buf[bufOffsPlusPlus] = input // buf.size == blockSize if ((bufOffsPlusPlus + 1) != buf.size) return - doCompression(buf, 0) + compressProtected(buf, 0) + bufOffsSet(0) } @Suppress("NOTHING_TO_INLINE") @@ -108,8 +66,7 @@ internal inline fun Buffer.commonUpdate( len: Int, bufOffs: Int, bufOffsSet: (value: Int) -> Unit, - compress: (buf: ByteArray, offset: Int) -> Unit, - compressCountIncrement: () -> Unit, + compressProtected: (buf: ByteArray, offset: Int) -> Unit, ) { val buf = value val blockSize = buf.size @@ -130,10 +87,9 @@ internal inline fun Buffer.commonUpdate( // Add enough input to do a compression val needed = blockSize - offsBuf input.copyInto(buf, offsBuf, offsInput, offsInput + needed) - compress(buf, 0) + compressProtected(buf, 0) offsBuf = 0 offsInput += needed - compressCountIncrement() } // Chunk blocks (if possible) @@ -147,24 +103,10 @@ internal inline fun Buffer.commonUpdate( break } - compress(input, offsInput) + compressProtected(input, offsInput) offsInput = offsNext - compressCountIncrement() } // Update globals bufOffsSet(offsBuf) } - -@Suppress("NOTHING_TO_INLINE") -internal inline fun Buffer.commonDigest( - bufOffs: Int, - compressCount: Long, - digest: (bitLength: Long, offs: Int, buf: ByteArray) -> ByteArray, - reset: () -> Unit, -): ByteArray { - val bitLength = ((compressCount * value.size) + bufOffs) * 8 - val final = digest(bitLength, bufOffs, value) - reset() - return final -} diff --git a/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-CommonPlatform.kt b/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-CommonPlatform.kt index c9c1316..1aea6db 100644 --- a/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-CommonPlatform.kt +++ b/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-CommonPlatform.kt @@ -30,12 +30,3 @@ internal inline fun ByteArray.commonCheckArgs(offset: Int, len: Int) { if (size - offset < len) throw IllegalArgumentException("Input too short") if (offset < 0 || len < 0 || offset > size - len) throw IndexOutOfBoundsException() } - -@Suppress("NOTHING_TO_INLINE") -internal inline fun Int.commonCalculateCompressions(multiplier: Int): Long { - var count = this.toLong() - if (multiplier > 0) { - count += (Int.MAX_VALUE.toLong() * multiplier) - } - return count -} diff --git a/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/DigestState.kt b/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/DigestState.kt index 3c7bc8a..0b0ee6a 100644 --- a/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/DigestState.kt +++ b/library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/DigestState.kt @@ -15,17 +15,5 @@ **/ package org.kotlincrypto.core.digest.internal -/** - * Used as a holder for copying/cloning of digests. - * - * **NOTE:** Can only be consumed once to instantiate a new Digest. This - * instance should **not** be held onto and should only be utilized by - * the `copy` function's implementation. - * */ -public sealed class DigestState( - internal val algorithm: String, - internal val digestLength: Int, - internal val bufOffs: Int, - internal val compressCount: Int, - internal val compressCountMultiplier: Int, -) +@Deprecated("Replaced by Digest.State since 1.0.0") +public sealed class DigestState diff --git a/library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/DigestUnitTest.kt b/library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/DigestUnitTest.kt index dfe78d0..fdab63f 100644 --- a/library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/DigestUnitTest.kt +++ b/library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/DigestUnitTest.kt @@ -55,7 +55,7 @@ class DigestUnitTest: TestDigestException() { var resetCount = 0 val digest = TestDigest( - digest = { _, _, _ -> expected }, + digest = { _, _ -> expected }, reset = { resetCount++ } ) @@ -69,33 +69,33 @@ class DigestUnitTest: TestDigestException() { fun givenDigest_whenUpdated_thenChunksProperly() { val digest = TestDigest( // Return byte array sized to the offset - digest = { _, offset, _ -> ByteArray(offset) } + digest = { _, offset -> ByteArray(offset) } ) digest.update(ByteArray(digest.blockSize() - 1)) - assertEquals(0, digest.compressCount()) + assertEquals(0, digest.compressions) digest.update(ByteArray(digest.blockSize() + 1)) - assertEquals(2, digest.compressCount()) + assertEquals(2, digest.compressions) digest.update(ByteArray(digest.blockSize() - 1)) - assertEquals(2, digest.compressCount()) + assertEquals(2, digest.compressions) digest.update(4) - assertEquals(3, digest.compressCount()) + assertEquals(3, digest.compressions) digest.update(ByteArray(digest.blockSize())) - assertEquals(4, digest.compressCount()) + assertEquals(4, digest.compressions) // Check the internal bufferOffset was 0 after all that assertEquals(0, digest.digest().size) - assertEquals(0, digest.compressCount()) + assertEquals(0, digest.compressions) } @Test fun givenDigest_whenCopied_thenIsNewInstance() { val digest = TestDigest( - digest = { _, _, b -> + digest = { b, _ -> assertEquals(1, b[0]) assertEquals(0, b[1]) b diff --git a/library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/TestDigest.kt b/library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/TestDigest.kt index 570af3c..41b4921 100644 --- a/library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/TestDigest.kt +++ b/library/digest/src/commonTest/kotlin/org/kotlincrypto/core/digest/TestDigest.kt @@ -15,22 +15,23 @@ **/ package org.kotlincrypto.core.digest -import org.kotlincrypto.core.digest.internal.DigestState - class TestDigest: Digest { private val compress: (input: ByteArray, offset: Int) -> Unit - private val finalize: (Long, Int, ByteArray) -> ByteArray + private val finalize: (buffer: ByteArray, offset: Int) -> ByteArray private val reset: () -> Unit constructor(algorithm: String): this(algorithm, 64) + var compressions: Int = 0 + private set + constructor( algorithm: String = "TEST", blockSize: Int = 64, digestLength: Int = 32, compress: (input: ByteArray, offset: Int) -> Unit = { _, _ -> }, - digest: (bitLength: Long, bufferOffset: Int, buffer: ByteArray) -> ByteArray = { _, _, _ -> ByteArray(blockSize) }, + digest: (buffer: ByteArray, offset: Int) -> ByteArray = { _, _ -> ByteArray(blockSize) }, reset: () -> Unit = {}, ): super(algorithm, blockSize, digestLength) { this.compress = compress @@ -39,9 +40,9 @@ class TestDigest: Digest { } private constructor( - state: DigestState, + state: State, compress: (input: ByteArray, offset: Int) -> Unit, - digest: (bitLength: Long, bufferOffset: Int, buffer: ByteArray) -> ByteArray, + digest: (buffer: ByteArray, offset: Int) -> ByteArray, reset: () -> Unit, ): super(state) { this.compress = compress @@ -49,21 +50,23 @@ class TestDigest: Digest { this.reset = reset } - fun compressCount(): Long = compressions() - - override fun compress(input: ByteArray, offset: Int) { + override fun compressProtected(input: ByteArray, offset: Int) { compress.invoke(input, offset) + compressions++ } - override fun digest(bitLength: Long, bufferOffset: Int, buffer: ByteArray): ByteArray { - return finalize.invoke(bitLength, bufferOffset, buffer) + override fun digestProtected(buffer: ByteArray, offset: Int): ByteArray { + return finalize.invoke(buffer, offset) } - override fun resetDigest() { + override fun resetProtected() { reset.invoke() + compressions = 0 } - override fun copy(state: DigestState): Digest { - return TestDigest(state, compress, finalize, reset) + override fun copyProtected(state: State): Digest { + val d = TestDigest(state, compress, finalize, reset) + d.compressions = compressions + return d } } diff --git a/library/digest/src/jvmMain/java9/module-info.java b/library/digest/src/jvmMain/java9/module-info.java index 2997bc4..896be50 100644 --- a/library/digest/src/jvmMain/java9/module-info.java +++ b/library/digest/src/jvmMain/java9/module-info.java @@ -3,5 +3,4 @@ requires transitive org.kotlincrypto.core; exports org.kotlincrypto.core.digest; - exports org.kotlincrypto.core.digest.internal; } diff --git a/library/digest/src/jvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt b/library/digest/src/jvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt index 9a0273b..f72e26d 100644 --- a/library/digest/src/jvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt +++ b/library/digest/src/jvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt @@ -19,7 +19,6 @@ package org.kotlincrypto.core.digest import org.kotlincrypto.core.* import org.kotlincrypto.core.digest.internal.* -import org.kotlincrypto.core.digest.internal.Buffer.Companion.buf import org.kotlincrypto.core.digest.internal.commonCheckArgs import org.kotlincrypto.core.digest.internal.commonToString import java.nio.ByteBuffer @@ -43,8 +42,6 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab private val digestLength: Int private val buf: Buffer private var bufOffs: Int - private var compressCount: Int - private var compressCountMultiplier: Int /** * Creates a new [Digest] for the specified parameters. @@ -63,8 +60,6 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab this.buf = Buffer.initialize(algorithm, blockSize, digestLength) this.digestLength = digestLength this.bufOffs = 0 - this.compressCount = 0 - this.compressCountMultiplier = 0 } /** @@ -72,7 +67,7 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab * instance. * * Implementors of [Digest] should have a private secondary constructor - * that is utilized by its [copy] implementation. + * that is utilized by its [copyProtected] implementation. * * e.g. * @@ -81,23 +76,17 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab * public constructor(): super("SHA-256", 64, 32) { * // Initialize... * } - * private constructor(thiz: SHA256, state: DigestState): super(state) { + * private constructor(thiz: SHA256, state: State): super(state) { * // Copy implementation details... * } - * protected override fun copy(state: DigestState): Digest = SHA256(this, state) + * protected override fun copyProtected(state: State): Digest = SHA256(this, state) * // ... * } - * - * @see [DigestState] - * @throws [IllegalStateException] If [DigestState] has already been used to instantiate - * another instance of [Digest] * */ - protected actual constructor(state: DigestState): super(state.algorithm) { + protected actual constructor(state: State): super((state as RealState).algorithm) { this.digestLength = state.digestLength - this.buf = state.buf() + this.buf = state.buf.copy() this.bufOffs = state.bufOffs - this.compressCount = state.compressCount - this.compressCountMultiplier = state.compressCountMultiplier } /** @@ -118,28 +107,27 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab // See Updatable interface documentation public actual final override fun update(input: Byte) { - updateDigest(input) + updateProtected(input) } // See Updatable interface documentation public actual final override fun update(input: ByteArray) { - updateDigest(input, 0, input.size) + updateProtected(input, 0, input.size) } // See Updatable interface documentation public actual final override fun update(input: ByteArray, offset: Int, len: Int) { input.commonCheckArgs(offset, len) - updateDigest(input, offset, len) + updateProtected(input, offset, len) } /** * Completes the computation, performing final operations and returning * the resultant array of bytes. The [Digest] is [reset] afterward. * */ - public actual final override fun digest(): ByteArray = buf.commonDigest( - bufOffs = bufOffs, - compressCount = compressions(), - digest = ::digest, - reset = ::reset, - ) + public actual final override fun digest(): ByteArray { + val final = digestProtected(buf.value, bufOffs) + reset() + return final + } /** * Updates the instance with provided [input], then completes the computation, @@ -147,7 +135,7 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab * [Digest] is [reset] afterward. * */ public actual final override fun digest(input: ByteArray): ByteArray { - updateDigest(input, 0, input.size) + updateProtected(input, 0, input.size) return digest() } @@ -155,69 +143,49 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab public actual final override fun reset() { buf.value.fill(0) bufOffs = 0 - compressCount = 0 - compressCountMultiplier = 0 - resetDigest() + resetProtected() } // See Copyable interface documentation - public actual final override fun copy(): Digest = buf.toState( - algorithm = algorithm, - digestLength = digestLength, - bufOffs = bufOffs, - compressCount = compressCount, - compressCountMultiplier = compressCountMultiplier, - ).let { copy(it) } + public actual final override fun copy(): Digest = copyProtected(RealState()) /** - * The number of compressions this [Digest] has completed. Backing variable - * is updated **after** completion of each [compress] invocation, and then - * subsequently set to `0` upon [reset] invocation. + * Used as a holder for copying digest internals. * */ - protected actual fun compressions(): Long = compressCount.commonCalculateCompressions(compressCountMultiplier) + protected actual sealed class State /** - * Called by the public [copy] function which produces the [DigestState] + * Called by the public [copy] function which produces the [State] * needed to create a wholly new instance. - * - * **NOTE:** [DigestState] can only be consumed once and should **NOT** - * be held on to. Attempting to instantiate multiple [Digest] instances - * with a single [DigestState] will raise an [IllegalStateException]. * */ - protected actual abstract fun copy(state: DigestState): Digest + protected actual abstract fun copyProtected(state: State): Digest /** * Called whenever a full [blockSize] worth of bytes are available for processing, * starting at index [offset] for the provided [input]. Implementations **must not** * alter [input]. * */ - protected actual abstract fun compress(input: ByteArray, offset: Int) + protected actual abstract fun compressProtected(input: ByteArray, offset: Int) /** * Called to complete the computation, providing any input that may be * buffered awaiting processing. * - * @param [bitLength] The number of bits that have been processed, including - * those remaining in the [buffer] - * @param [bufferOffset] The index at which the next input would be placed in - * the [buffer] * @param [buffer] Unprocessed input + * @param [offset] The index at which the next input would be placed in the [buffer] * */ - protected actual abstract fun digest(bitLength: Long, bufferOffset: Int, buffer: ByteArray): ByteArray + protected actual abstract fun digestProtected(buffer: ByteArray, offset: Int): ByteArray /** * Optional override for implementations to intercept cleansed input before * being processed by the [Digest] abstraction. * */ - protected actual open fun updateDigest(input: Byte) { + protected actual open fun updateProtected(input: Byte) { buf.commonUpdate( input = input, bufOffsPlusPlus = bufOffs++, - doCompression = { buf, offset -> - compress(buf, offset) - bufOffs = 0 - compressCountIncrement() - }, + bufOffsSet = { bufOffs = it }, + compressProtected = ::compressProtected, ) } @@ -226,26 +194,18 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab * being processed by the [Digest] abstraction. Parameters passed to this * function are always valid and have been checked for appropriateness. * */ - protected actual open fun updateDigest(input: ByteArray, offset: Int, len: Int) { + protected actual open fun updateProtected(input: ByteArray, offset: Int, len: Int) { buf.commonUpdate( input = input, offset = offset, len = len, bufOffs = bufOffs, bufOffsSet = { bufOffs = it }, - compress = ::compress, - compressCountIncrement = ::compressCountIncrement, + compressProtected = ::compressProtected, ) } - protected actual abstract fun resetDigest() - - private fun compressCountIncrement() { - if (++compressCount != Int.MIN_VALUE) return - // Int.MAX_VALUE reached and went negative - compressCount = 1 - compressCountMultiplier++ - } + protected actual abstract fun resetProtected() // MessageDigest /** @suppress */ @@ -258,7 +218,7 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab /** @suppress */ protected final override fun engineGetDigestLength(): Int = digestLength /** @suppress */ - protected final override fun engineUpdate(p0: Byte) { updateDigest(p0) } + protected final override fun engineUpdate(p0: Byte) { updateProtected(p0) } /** @suppress */ protected final override fun engineUpdate(input: ByteBuffer) { super.engineUpdate(input) } /** @suppress */ @@ -280,4 +240,11 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab public actual final override fun hashCode(): Int = buf.hashCode() /** @suppress */ public actual final override fun toString(): String = commonToString() + + private inner class RealState: State() { + val algorithm: String = this@Digest.algorithm() + val digestLength: Int = this@Digest.digestLength() + val bufOffs: Int = this@Digest.bufOffs + val buf: Buffer = this@Digest.buf + } } diff --git a/library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/JvmDigestUnitTest.kt b/library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/JvmDigestUnitTest.kt index 2256f40..6921858 100644 --- a/library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/JvmDigestUnitTest.kt +++ b/library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/JvmDigestUnitTest.kt @@ -15,14 +15,11 @@ **/ package org.kotlincrypto.core.digest -import org.kotlincrypto.core.digest.internal.DigestState -import org.kotlincrypto.core.digest.internal.commonCalculateCompressions import java.lang.AssertionError import java.security.MessageDigest import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertContentEquals -import kotlin.test.assertEquals /** * Compares [Digest] functionality to [MessageDigest] @@ -36,22 +33,22 @@ class JvmDigestUnitTest { constructor(digest: MessageDigest, blockSize: Int): super(digest.algorithm, blockSize, digest.digestLength) { delegate = digest.clone() as MessageDigest } - private constructor(state: DigestState, digest: MessageDigestWrap): super(state) { + private constructor(state: State, digest: MessageDigestWrap): super(state) { delegate = digest.delegate.clone() as MessageDigest } - override fun copy(state: DigestState): Digest = MessageDigestWrap(state, this) + override fun copyProtected(state: State): Digest = MessageDigestWrap(state, this) - override fun compress(input: ByteArray, offset: Int) { + override fun compressProtected(input: ByteArray, offset: Int) { delegate.update(input, offset, blockSize()) } - override fun digest(bitLength: Long, bufferOffset: Int, buffer: ByteArray): ByteArray { - delegate.update(buffer, 0, bufferOffset) + override fun digestProtected(buffer: ByteArray, offset: Int): ByteArray { + delegate.update(buffer, 0, offset) return delegate.digest() } - override fun resetDigest() { + override fun resetProtected() { delegate.reset() } } @@ -96,24 +93,4 @@ class JvmDigestUnitTest { assertContentEquals(jvm.digest(), wrap.digest()) } - - // Test live here because functions are internal and the `test-android` - // module has symbolic links to commonTest which will cause crying. - @Test - fun givenCompressCount_whenMultiplier_thenCalculatesCorrectly() { - var actual = 0.commonCalculateCompressions(multiplier = 0) - assertEquals(0, actual) - - actual = 1.commonCalculateCompressions(multiplier = 0) - assertEquals(1, actual) - - actual = 0.commonCalculateCompressions(multiplier = 1) - assertEquals(Int.MAX_VALUE.toLong(), actual) - - actual = 1.commonCalculateCompressions(multiplier = 1) - assertEquals(Int.MAX_VALUE.toLong() + 1, actual) - - actual = 5.commonCalculateCompressions(multiplier = 10) - assertEquals((Int.MAX_VALUE.toLong() * 10) + 5, actual) - } } diff --git a/library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/internal/BufferUnitTest.kt b/library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/internal/BufferUnitTest.kt deleted file mode 100644 index 26e5f52..0000000 --- a/library/digest/src/jvmTest/kotlin/org/kotlincrypto/core/digest/internal/BufferUnitTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.core.digest.internal - -import org.kotlincrypto.core.digest.internal.Buffer.Companion.buf -import kotlin.test.Test -import kotlin.test.assertFailsWith - -class BufferUnitTest { - - @Test - fun givenBufferDigestState_whenAlreadyConsumed_thenFails() { - val buf = Buffer.initialize("test", 8, 0) - val state = buf.toState("test", 0, 0, 0, 0) - state.buf() - assertFailsWith { state.buf() } - } -} diff --git a/library/digest/src/nonJvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt b/library/digest/src/nonJvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt index f15bd96..48daac4 100644 --- a/library/digest/src/nonJvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt +++ b/library/digest/src/nonJvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt @@ -19,9 +19,6 @@ package org.kotlincrypto.core.digest import org.kotlincrypto.core.* import org.kotlincrypto.core.digest.internal.* -import org.kotlincrypto.core.digest.internal.Buffer.Companion.buf -import org.kotlincrypto.core.digest.internal.commonCheckArgs -import org.kotlincrypto.core.digest.internal.commonToString /** * Core abstraction for Message Digest implementations. @@ -40,8 +37,6 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up private val digestLength: Int private val buf: Buffer private var bufOffs: Int - private var compressCount: Int - private var compressCountMultiplier: Int /** * Creates a new [Digest] for the specified parameters. @@ -61,8 +56,6 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up this.algorithm = algorithm this.digestLength = digestLength this.bufOffs = 0 - this.compressCount = 0 - this.compressCountMultiplier = 0 } /** @@ -70,7 +63,7 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up * instance. * * Implementors of [Digest] should have a private secondary constructor - * that is utilized by its [copy] implementation. + * that is utilized by its [copyProtected] implementation. * * e.g. * @@ -79,24 +72,18 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up * public constructor(): super("SHA-256", 64, 32) { * // Initialize... * } - * private constructor(thiz: SHA256, state: DigestState): super(state) { + * private constructor(thiz: SHA256, state: State): super(state) { * // Copy implementation details... * } - * protected override fun copy(state: DigestState): Digest = SHA256(this, state) + * protected override fun copyProtected(state: State): Digest = SHA256(this, state) * // ... * } - * - * @see [DigestState] - * @throws [IllegalStateException] If [DigestState] has already been used to instantiate - * another instance of [Digest] * */ - protected actual constructor(state: DigestState) { - this.algorithm = state.algorithm + protected actual constructor(state: State) { + this.algorithm = (state as RealState).algorithm this.digestLength = state.digestLength - this.buf = state.buf() + this.buf = state.buf.copy() this.bufOffs = state.bufOffs - this.compressCount = state.compressCount - this.compressCountMultiplier = state.compressCountMultiplier } /** @@ -116,29 +103,28 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up public actual final override fun algorithm(): String = algorithm // See Updatable interface documentation - public actual override fun update(input: Byte) { - updateDigest(input) + public actual final override fun update(input: Byte) { + updateProtected(input) } // See Updatable interface documentation public actual final override fun update(input: ByteArray) { - updateDigest(input, 0, input.size) + updateProtected(input, 0, input.size) } // See Updatable interface documentation public actual final override fun update(input: ByteArray, offset: Int, len: Int) { input.commonCheckArgs(offset, len) - updateDigest(input, offset, len) + updateProtected(input, offset, len) } /** * Completes the computation, performing final operations and returning * the resultant array of bytes. The [Digest] is [reset] afterward. * */ - public actual fun digest(): ByteArray = buf.commonDigest( - bufOffs = bufOffs, - compressCount = compressions(), - digest = ::digest, - reset = ::reset, - ) + public actual fun digest(): ByteArray { + val final = digestProtected(buf.value, bufOffs) + reset() + return final + } /** * Updates the instance with provided [input], then completes the computation, @@ -146,7 +132,7 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up * [Digest] is [reset] afterward. * */ public actual fun digest(input: ByteArray): ByteArray { - updateDigest(input, 0, input.size) + updateProtected(input, 0, input.size) return digest() } @@ -154,69 +140,48 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up public actual final override fun reset() { buf.value.fill(0) bufOffs = 0 - compressCount = 0 - compressCountMultiplier = 0 - resetDigest() + resetProtected() } // See Copyable interface documentation - public actual final override fun copy(): Digest = buf.toState( - algorithm = algorithm, - digestLength = digestLength, - bufOffs = bufOffs, - compressCount = compressCount, - compressCountMultiplier = compressCountMultiplier, - ).let { copy(it) } + public actual final override fun copy(): Digest = copyProtected(RealState()) /** - * The number of compressions this [Digest] has completed. Backing - * variable is updated **after** each [compress] invocation, and - * subsequently set to `0` upon [reset] invocation. + * Used as a holder for copying digest internals. * */ - protected actual fun compressions(): Long = compressCount.commonCalculateCompressions(compressCountMultiplier) - + protected actual sealed class State /** - * Called by the public [copy] function which produces the [DigestState] - * needed to create a wholly new instance. - * - * **NOTE:** [DigestState] can only be consumed once and should **NOT** - * be held on to. Attempting to instantiate multiple [Digest] instances - * with a single [DigestState] will raise an [IllegalStateException]. + * Called by the [copy] function which produces the [State] needed + * to create a wholly new instance. * */ - protected actual abstract fun copy(state: DigestState): Digest + protected actual abstract fun copyProtected(state: State): Digest /** * Called whenever a full [blockSize] worth of bytes are available for processing, * starting at index [offset] for the provided [input]. Implementations **must not** * alter [input]. * */ - protected actual abstract fun compress(input: ByteArray, offset: Int) + protected actual abstract fun compressProtected(input: ByteArray, offset: Int) /** * Called to complete the computation, providing any input that may be * buffered awaiting processing. * - * @param [bitLength] The number of bits that have been processed, including - * those remaining in the [buffer] - * @param [bufferOffset] The index at which the next input would be placed in - * the [buffer] * @param [buffer] Unprocessed input + * @param [offset] The index at which the next input would be placed in the [buffer] * */ - protected actual abstract fun digest(bitLength: Long, bufferOffset: Int, buffer: ByteArray): ByteArray + protected actual abstract fun digestProtected(buffer: ByteArray, offset: Int): ByteArray /** * Optional override for implementations to intercept cleansed input before * being processed by the [Digest] abstraction. * */ - protected actual open fun updateDigest(input: Byte) { + protected actual open fun updateProtected(input: Byte) { buf.commonUpdate( input = input, bufOffsPlusPlus = bufOffs++, - doCompression = { buf, offset -> - compress(buf, offset) - bufOffs = 0 - compressCountIncrement() - }, + bufOffsSet = { bufOffs = it }, + compressProtected = ::compressProtected, ) } @@ -225,26 +190,18 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up * being processed by the [Digest] abstraction. Parameters passed to this * function are always valid and have been checked for appropriateness. * */ - protected actual open fun updateDigest(input: ByteArray, offset: Int, len: Int) { + protected actual open fun updateProtected(input: ByteArray, offset: Int, len: Int) { buf.commonUpdate( input = input, offset = offset, len = len, bufOffs = bufOffs, bufOffsSet = { bufOffs = it }, - compress = ::compress, - compressCountIncrement = ::compressCountIncrement, + compressProtected = ::compressProtected, ) } - protected actual abstract fun resetDigest() - - private fun compressCountIncrement() { - if (++compressCount != Int.MIN_VALUE) return - // Int.MAX_VALUE reached and went negative - compressCount = 1 - compressCountMultiplier++ - } + protected actual abstract fun resetProtected() /** @suppress */ public actual final override fun equals(other: Any?): Boolean = other is Digest && other.buf == buf @@ -252,4 +209,11 @@ public actual abstract class Digest: Algorithm, Copyable, Resettable, Up public actual final override fun hashCode(): Int = buf.hashCode() /** @suppress */ public actual final override fun toString(): String = commonToString() + + private inner class RealState: State() { + val algorithm: String = this@Digest.algorithm() + val digestLength: Int = this@Digest.digestLength() + val bufOffs: Int = this@Digest.bufOffs + val buf: Buffer = this@Digest.buf + } }