Skip to content

Commit 1dc7507

Browse files
authored
Add Mac.doFinalInto functionality (#109)
1 parent e14c9db commit 1dc7507

File tree

11 files changed

+247
-25
lines changed

11 files changed

+247
-25
lines changed

library/digest/src/commonMain/kotlin/org/kotlincrypto/core/digest/internal/-CommonPlatform.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
**/
16-
@file:Suppress("KotlinRedundantDiagnosticSuppress")
16+
@file:Suppress("KotlinRedundantDiagnosticSuppress", "NOTHING_TO_INLINE")
1717

1818
package org.kotlincrypto.core.digest.internal
1919

@@ -22,19 +22,17 @@ import kotlin.contracts.ExperimentalContracts
2222
import kotlin.contracts.InvocationKind
2323
import kotlin.contracts.contract
2424

25-
@Suppress("NOTHING_TO_INLINE")
2625
internal inline fun Digest.commonToString(): String {
2726
return "Digest[${algorithm()}]@${hashCode()}"
2827
}
2928

3029
@Throws(Exception::class)
31-
@Suppress("NOTHING_TO_INLINE")
3230
@OptIn(ExperimentalContracts::class)
3331
internal inline fun ByteArray.commonCheckArgs(
3432
offset: Int,
3533
len: Int,
3634
onShortInput: () -> Exception = { IllegalArgumentException("Input too short") },
37-
onOutOfBounds: (message: String) -> Exception = { IndexOutOfBoundsException(it) },
35+
onOutOfBounds: (message: String) -> Exception = { message -> IndexOutOfBoundsException(message) },
3836
) {
3937
contract {
4038
callsInPlace(onShortInput, InvocationKind.AT_MOST_ONCE)

library/digest/src/jvmMain/kotlin/org/kotlincrypto/core/digest/Digest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ public actual abstract class Digest: MessageDigest, Algorithm, Cloneable, Copyab
254254
@Deprecated("Use digestInto", ReplaceWith("digestInto(buf, offset)"))
255255
public final override fun digest(buf: ByteArray?, offset: Int, len: Int): Int {
256256
requireNotNull(buf) { "buf cannot be null" }
257-
buf.commonCheckArgs(offset, len, onOutOfBounds = { reason -> DigestException(reason) })
257+
buf.commonCheckArgs(offset, len, onOutOfBounds = { message -> DigestException(message) })
258258
@Suppress("DEPRECATION")
259259
return engineDigest(buf, offset, len)
260260
}

library/mac/api/mac.api

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ public abstract class org/kotlincrypto/core/mac/Mac : javax/crypto/Mac, org/kotl
33
protected fun <init> (Lorg/kotlincrypto/core/mac/Mac;)V
44
public final fun algorithm ()Ljava/lang/String;
55
public final fun clearKey ()V
6+
public final fun doFinalInto ([BI)I
67
public final fun equals (Ljava/lang/Object;)Z
78
public final fun hashCode ()I
89
public final fun macLength ()I
@@ -15,6 +16,7 @@ protected abstract class org/kotlincrypto/core/mac/Mac$Engine : javax/crypto/Mac
1516
public fun <init> ([B)V
1617
public final fun clone ()Ljava/lang/Object;
1718
public abstract fun doFinal ()[B
19+
public fun doFinalInto ([BI)V
1820
protected final fun engineDoFinal ()[B
1921
protected final fun engineGetMacLength ()I
2022
protected final fun engineInit (Ljava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V

library/mac/api/mac.klib.api

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ abstract class org.kotlincrypto.core.mac/Mac : org.kotlincrypto.core/Algorithm,
1414
final fun clearKey() // org.kotlincrypto.core.mac/Mac.clearKey|clearKey(){}[0]
1515
final fun doFinal(): kotlin/ByteArray // org.kotlincrypto.core.mac/Mac.doFinal|doFinal(){}[0]
1616
final fun doFinal(kotlin/ByteArray): kotlin/ByteArray // org.kotlincrypto.core.mac/Mac.doFinal|doFinal(kotlin.ByteArray){}[0]
17+
final fun doFinalInto(kotlin/ByteArray, kotlin/Int): kotlin/Int // org.kotlincrypto.core.mac/Mac.doFinalInto|doFinalInto(kotlin.ByteArray;kotlin.Int){}[0]
1718
final fun equals(kotlin/Any?): kotlin/Boolean // org.kotlincrypto.core.mac/Mac.equals|equals(kotlin.Any?){}[0]
1819
final fun hashCode(): kotlin/Int // org.kotlincrypto.core.mac/Mac.hashCode|hashCode(){}[0]
1920
final fun macLength(): kotlin/Int // org.kotlincrypto.core.mac/Mac.macLength|macLength(){}[0]
@@ -33,6 +34,7 @@ abstract class org.kotlincrypto.core.mac/Mac : org.kotlincrypto.core/Algorithm,
3334
abstract fun reset(kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.Engine.reset|reset(kotlin.ByteArray){}[0]
3435
final fun equals(kotlin/Any?): kotlin/Boolean // org.kotlincrypto.core.mac/Mac.Engine.equals|equals(kotlin.Any?){}[0]
3536
final fun hashCode(): kotlin/Int // org.kotlincrypto.core.mac/Mac.Engine.hashCode|hashCode(){}[0]
37+
open fun doFinalInto(kotlin/ByteArray, kotlin/Int) // org.kotlincrypto.core.mac/Mac.Engine.doFinalInto|doFinalInto(kotlin.ByteArray;kotlin.Int){}[0]
3638
open fun update(kotlin/ByteArray) // org.kotlincrypto.core.mac/Mac.Engine.update|update(kotlin.ByteArray){}[0]
3739
}
3840
}

library/mac/src/commonMain/kotlin/org/kotlincrypto/core/mac/Mac.kt

+31-3
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,24 @@ public expect abstract class Mac: Algorithm, Copyable<Mac>, Resettable, Updatabl
9494
public fun doFinal(): ByteArray
9595

9696
/**
97-
* Updates the instance with provided [input], then completes the computation,
97+
* Updates the instance with provided [input] then completes the computation,
9898
* performing final operations and returning the resultant array of bytes. The
9999
* [Mac] is [reset] afterward.
100100
* */
101101
public fun doFinal(input: ByteArray): ByteArray
102102

103+
/**
104+
* Completes the computation, performing final operations and placing the
105+
* resultant bytes into the provided [dest] array starting at index [destOffset].
106+
* The [Mac] is [reset] afterward.
107+
*
108+
* @return The number of bytes put into [dest] (i.e. the [macLength])
109+
* @throws [IndexOutOfBoundsException] if [destOffset] is inappropriate
110+
* @throws [ShortBufferException] if [macLength] number of bytes are unable
111+
* to fit into [dest] for provided [destOffset]
112+
* */
113+
public fun doFinalInto(dest: ByteArray, destOffset: Int): Int
114+
103115
// See Resettable interface documentation
104116
public final override fun reset()
105117

@@ -146,14 +158,30 @@ public expect abstract class Mac: Algorithm, Copyable<Mac>, Resettable, Updatabl
146158
* */
147159
public abstract fun macLength(): Int
148160

161+
// See Updatable interface documentation
162+
public override fun update(input: ByteArray)
163+
149164
/**
150165
* Completes the computation, performing final operations and returning
151166
* the resultant array of bytes. The [Engine] is [reset] afterward.
152167
* */
153168
public abstract fun doFinal(): ByteArray
154169

155-
// See Updatable interface documentation
156-
public override fun update(input: ByteArray)
170+
/**
171+
* Called to complete the computation, performing final operations and placing
172+
* the resultant bytes into the provided [dest] array starting at index [destOffset].
173+
* The [Engine] is [reset] afterward.
174+
*
175+
* Implementations should override this addition to the API for performance reasons.
176+
* If overridden, `super.doFinalInto` should **not** be called.
177+
*
178+
* **NOTE:** The public [Mac.doFinalInto] function always checks [dest] for capacity
179+
* of [macLength], starting at [destOffset], before calling this function.
180+
*
181+
* @param [dest] The array to place resultant bytes
182+
* @param [destOffset] The index to begin placing bytes into [dest]
183+
* */
184+
public open fun doFinalInto(dest: ByteArray, destOffset: Int)
157185

158186
/**
159187
* Resets the [Engine] and will reinitialize it with the provided key.

library/mac/src/commonMain/kotlin/org/kotlincrypto/core/mac/internal/-CommonPlatform.kt

+50
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717

1818
package org.kotlincrypto.core.mac.internal
1919

20+
import org.kotlincrypto.core.ShortBufferException
2021
import org.kotlincrypto.core.mac.Mac
22+
import kotlin.contracts.ExperimentalContracts
23+
import kotlin.contracts.InvocationKind
24+
import kotlin.contracts.contract
2125

2226
private val SINGLE_0BYTE_KEY = ByteArray(1) { 0 }
2327

@@ -31,7 +35,31 @@ internal inline fun Mac.commonToString(): String {
3135
return "Mac[${algorithm()}]@${hashCode()}"
3236
}
3337

38+
@Throws(Exception::class)
39+
@OptIn(ExperimentalContracts::class)
40+
internal inline fun ByteArray.commonCheckArgs(
41+
offset: Int,
42+
len: Int,
43+
onShortInput: () -> Exception = { IllegalArgumentException("Input too short") },
44+
onOutOfBounds: (message: String) -> Exception = { message -> IndexOutOfBoundsException(message) },
45+
) {
46+
contract {
47+
callsInPlace(onShortInput, InvocationKind.AT_MOST_ONCE)
48+
callsInPlace(onOutOfBounds, InvocationKind.AT_MOST_ONCE)
49+
}
50+
51+
if (size - offset < len) throw onShortInput()
52+
if (offset < 0) throw onOutOfBounds("offset[$offset] < 0")
53+
if (len < 0) throw onOutOfBounds("len[$len] < 0")
54+
if (offset > size - len) throw onOutOfBounds("offset[$offset] > size[$size] - len[$len]")
55+
}
56+
57+
@OptIn(ExperimentalContracts::class)
3458
internal inline fun Mac.commonClearKey(engineReset: (ByteArray) -> Unit) {
59+
contract {
60+
callsInPlace(engineReset, InvocationKind.AT_LEAST_ONCE)
61+
}
62+
3563
try {
3664
engineReset(SINGLE_0BYTE_KEY)
3765
} catch (e1: IllegalArgumentException) {
@@ -43,3 +71,25 @@ internal inline fun Mac.commonClearKey(engineReset: (ByteArray) -> Unit) {
4371
}
4472
}
4573
}
74+
75+
@OptIn(ExperimentalContracts::class)
76+
@Throws(IndexOutOfBoundsException::class, ShortBufferException::class)
77+
internal inline fun Mac.commonDoFinalInto(
78+
dest: ByteArray,
79+
destOffset: Int,
80+
engineDoFinalInto: (dest: ByteArray, destOffset: Int) -> Unit,
81+
engineReset: () -> Unit,
82+
): Int {
83+
contract {
84+
callsInPlace(engineDoFinalInto, InvocationKind.AT_MOST_ONCE)
85+
callsInPlace(engineReset, InvocationKind.AT_MOST_ONCE)
86+
}
87+
88+
val len = macLength()
89+
dest.commonCheckArgs(destOffset, len, onShortInput = {
90+
ShortBufferException("Not enough room in dest for $len bytes")
91+
})
92+
engineDoFinalInto(dest, destOffset)
93+
engineReset()
94+
return len
95+
}

library/mac/src/commonTest/kotlin/org/kotlincrypto/core/mac/MacUnitTest.kt

+55-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
**/
1616
package org.kotlincrypto.core.mac
1717

18+
import org.kotlincrypto.core.ShortBufferException
1819
import kotlin.test.*
1920

2021
class MacUnitTest {
@@ -63,22 +64,31 @@ class MacUnitTest {
6364
fun givenMac_whenDoFinal_thenEngineResetIsCalled() {
6465
var resetCount = 0
6566
var doFinalCount = 0
66-
val finalExpected = ByteArray(20)
67+
val finalExpected = ByteArray(20) { it.toByte() }
6768

6869
val mac = TestMac(
6970
ByteArray(5),
7071
"not blank",
72+
macLen = finalExpected.size,
7173
reset = { resetCount++ },
7274
doFinal = { doFinalCount++; finalExpected },
7375
)
7476
mac.update(ByteArray(20))
77+
78+
// doFinal
7579
assertEquals(finalExpected, mac.doFinal())
7680
assertEquals(1, doFinalCount)
7781
assertEquals(1, resetCount)
7882

83+
// update & doFinal
7984
assertEquals(finalExpected, mac.doFinal(ByteArray(20)))
8085
assertEquals(2, doFinalCount)
8186
assertEquals(2, resetCount)
87+
88+
// doFinalInto
89+
assertEquals(finalExpected.size, mac.doFinalInto(ByteArray(25), 0))
90+
assertEquals(3, doFinalCount)
91+
assertEquals(3, resetCount)
8292
}
8393

8494
@Test
@@ -94,4 +104,48 @@ class MacUnitTest {
94104
assertNotNull(zeroKey)
95105
assertContentEquals(ByteArray(1) { 0 }, zeroKey)
96106
}
107+
108+
@Test
109+
fun givenMac_whenDoFinalInto_thenDefaultImplementationCopiesResultIntoDest() {
110+
val expected = ByteArray(10) { 1 }
111+
val mac = TestMac(
112+
key = ByteArray(5),
113+
algorithm = "test doFinalInto",
114+
macLen = expected.size,
115+
doFinal = { expected.copyOf() }
116+
)
117+
val actual = ByteArray(expected.size + 2) { 4 }
118+
mac.doFinalInto(actual, 1)
119+
120+
assertEquals(4, actual[0])
121+
assertEquals(4, actual[actual.size - 1])
122+
for (i in expected.indices) {
123+
assertEquals(expected[i], actual[i + 1])
124+
}
125+
}
126+
127+
@Test
128+
fun givenMac_whenLength0_thenDoFinalIntoDoesNotFail() {
129+
val mac = TestMac(
130+
key = ByteArray(5),
131+
algorithm = "test doFinalInto 0",
132+
macLen = 0
133+
)
134+
135+
mac.doFinalInto(ByteArray(0), 0)
136+
mac.doFinalInto(ByteArray(2), 1)
137+
mac.doFinalInto(ByteArray(2), 2)
138+
}
139+
140+
@Test
141+
fun givenMac_whenDoFinalInto_thenThrowsExceptionsAsExpected() {
142+
val mac = TestMac(
143+
key = ByteArray(5),
144+
algorithm = "test doFinalInto Exceptions",
145+
macLen = 5,
146+
)
147+
val mSize = mac.macLength()
148+
assertFailsWith<ShortBufferException> { mac.doFinalInto(ByteArray(mSize), 1) }
149+
assertFailsWith<IndexOutOfBoundsException> { mac.doFinalInto(ByteArray(mSize), -1) }
150+
}
97151
}

library/mac/src/commonTest/kotlin/org/kotlincrypto/core/mac/TestMac.kt

+8-3
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ class TestMac : Mac {
2020
constructor(
2121
key: ByteArray,
2222
algorithm: String,
23+
macLen: Int = 0,
2324
reset: () -> Unit = {},
2425
rekey: (new: ByteArray) -> Unit = {},
25-
doFinal: () -> ByteArray = { ByteArray(0) },
26-
): super(algorithm, TestEngine(key, reset, rekey, doFinal))
26+
doFinal: () -> ByteArray = { ByteArray(macLen) },
27+
): super(algorithm, TestEngine(key, reset, rekey, doFinal, macLen))
2728

2829
private constructor(algorithm: String, engine: TestEngine): super(algorithm, engine)
2930
private constructor(other: TestMac): super(other)
@@ -35,22 +36,26 @@ class TestMac : Mac {
3536
private val reset: () -> Unit
3637
private val rekey: (new: ByteArray) -> Unit
3738
private val doFinal: () -> ByteArray
39+
private val macLen: Int
3840

3941
constructor(
4042
key: ByteArray,
4143
reset: () -> Unit,
4244
rekey: (new: ByteArray) -> Unit,
4345
doFinal: () -> ByteArray,
46+
macLen: Int,
4447
): super(key) {
4548
this.reset = reset
4649
this.rekey = rekey
4750
this.doFinal = doFinal
51+
this.macLen = macLen
4852
}
4953

5054
private constructor(other: TestEngine): super(other) {
5155
this.reset = other.reset
5256
this.rekey = other.rekey
5357
this.doFinal = other.doFinal
58+
this.macLen = other.macLen
5459
}
5560

5661
// To ensure that Java implementation initializes javax.crypto.Mac
@@ -62,7 +67,7 @@ class TestMac : Mac {
6267
override fun reset(newKey: ByteArray) { rekey.invoke(newKey) }
6368
override fun update(input: ByteArray) {}
6469
override fun update(input: ByteArray, offset: Int, len: Int) {}
65-
override fun macLength(): Int = 0
70+
override fun macLength(): Int = macLen
6671
override fun doFinal(): ByteArray = doFinal.invoke()
6772

6873
override fun copy(): Engine = TestEngine(this)

0 commit comments

Comments
 (0)