diff --git a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/WithCrt.kt b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/WithCrt.kt new file mode 100644 index 00000000..737ceff0 --- /dev/null +++ b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/WithCrt.kt @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.crt + +/** + * A mixin class used to ensure CRT is initialized before the class is invoked + */ +public open class WithCrt { + init { + CRT.initRuntime { } + } +} diff --git a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/DigestNative.kt b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/DigestNative.kt index b71f644f..021ec8e6 100644 --- a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/DigestNative.kt +++ b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/DigestNative.kt @@ -2,10 +2,9 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ - package aws.sdk.kotlin.crt.util -import Sha256 +import aws.sdk.kotlin.crt.util.hashing.Sha256 /** * Utility object for various hash functions diff --git a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/CrcNative.kt b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/CrcNative.kt index fd671e39..34a60330 100644 --- a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/CrcNative.kt +++ b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/CrcNative.kt @@ -2,6 +2,9 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ +package aws.sdk.kotlin.crt.util.hashing + +import aws.sdk.kotlin.crt.WithCrt import kotlinx.cinterop.* import libcrt.aws_checksums_crc32 import libcrt.aws_checksums_crc32c @@ -21,6 +24,10 @@ internal class Crc(val checksumFn: AwsChecksumsCrcFunction) : HashFunction { private var crc = 0U override fun update(input: ByteArray, offset: Int, length: Int) { + if (input.isEmpty() || length == 0) { + return + } + val offsetInput = input.usePinned { it.addressOf(offset) } @@ -42,7 +49,9 @@ internal class Crc(val checksumFn: AwsChecksumsCrcFunction) : HashFunction { /** * A CRC32 [HashFunction] implemented using bindings to CRT. */ -public class Crc32 : HashFunction { +public class Crc32 : + WithCrt(), + HashFunction { private val crc32 = Crc(::aws_checksums_crc32) override fun update(input: ByteArray, offset: Int, length: Int) { crc32.update(input, offset, length) @@ -56,7 +65,9 @@ public class Crc32 : HashFunction { /** * A CRC32C [HashFunction] implemented using bindings to CRT. */ -public class Crc32c : HashFunction { +public class Crc32c : + WithCrt(), + HashFunction { private val crc32c = Crc(::aws_checksums_crc32c) override fun update(input: ByteArray, offset: Int, length: Int) { crc32c.update(input, offset, length) diff --git a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/HashFunctionNative.kt b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/HashFunctionNative.kt index 62a66122..04ca2b36 100644 --- a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/HashFunctionNative.kt +++ b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/HashFunctionNative.kt @@ -2,6 +2,8 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ +package aws.sdk.kotlin.crt.util.hashing + /** * A function which calculates the hash of given data */ diff --git a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/Md5Native.kt b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/Md5Native.kt new file mode 100644 index 00000000..03e9b13a --- /dev/null +++ b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/Md5Native.kt @@ -0,0 +1,65 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.crt.util.hashing + +import aws.sdk.kotlin.crt.Allocator +import aws.sdk.kotlin.crt.WithCrt +import aws.sdk.kotlin.crt.awsAssertOpSuccess +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.cValue +import kotlinx.cinterop.convert +import kotlinx.cinterop.pointed +import kotlinx.cinterop.reinterpret +import kotlinx.cinterop.usePinned +import libcrt.aws_byte_buf +import libcrt.aws_byte_cursor_from_array +import libcrt.aws_hash_destroy +import libcrt.aws_hash_finalize +import libcrt.aws_hash_update +import libcrt.aws_md5_new + +/** + * MD5 hash function implemented using bindings to CRT + */ +public class Md5 : + WithCrt(), + HashFunction { + private var md5 = checkNotNull(aws_md5_new(Allocator.Default)) { "aws_md5_new" } + + override fun update(input: ByteArray, offset: Int, length: Int) { + if (input.isEmpty() || length == 0) { + return + } + + val inputCursor = input.usePinned { + aws_byte_cursor_from_array(it.addressOf(offset), length.convert()) + } + + awsAssertOpSuccess(aws_hash_update(md5, inputCursor)) { + "aws_hash_update" + } + } + + override fun digest(): ByteArray { + val output = ByteArray(md5.pointed.digest_size.toInt()) + + val byteBuf = output.usePinned { + cValue { + capacity = output.size.convert() + len = 0U + buffer = it.addressOf(0).reinterpret() + } + } + + awsAssertOpSuccess(aws_hash_finalize(md5, byteBuf, 0U)) { "aws_hash_finalize" } + + return output.also { reset() } + } + + override fun reset() { + aws_hash_destroy(md5) + md5 = checkNotNull(aws_md5_new(Allocator.Default)) { "aws_md5_new" } + } +} diff --git a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/ShaNative.kt b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/ShaNative.kt index c8db8f9e..8db45e16 100644 --- a/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/ShaNative.kt +++ b/aws-crt-kotlin/native/src/aws/sdk/kotlin/crt/util/hashing/ShaNative.kt @@ -2,7 +2,10 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ +package aws.sdk.kotlin.crt.util.hashing + import aws.sdk.kotlin.crt.Allocator +import aws.sdk.kotlin.crt.WithCrt import aws.sdk.kotlin.crt.awsAssertOpSuccess import kotlinx.cinterop.* import libcrt.* @@ -17,7 +20,9 @@ internal typealias InitializeHashFn = ( /** * SHA-1 hash function implemented using bindings to CRT */ -public class Sha1 : HashFunction { +public class Sha1 : + WithCrt(), + HashFunction { private val sha1 = Sha(::aws_sha1_new) override fun update(input: ByteArray, offset: Int, length: Int) { sha1.update(input, offset, length) @@ -31,7 +36,9 @@ public class Sha1 : HashFunction { /** * SHA-256 hash function implemented using bindings to CRT */ -public class Sha256 : HashFunction { +public class Sha256 : + WithCrt(), + HashFunction { private val sha256 = Sha(::aws_sha256_new) override fun update(input: ByteArray, offset: Int, length: Int) { sha256.update(input, offset, length) @@ -51,6 +58,10 @@ internal class Sha(val initializeFn: InitializeHashFn) : HashFunction { // aws_hash_update override fun update(input: ByteArray, offset: Int, length: Int) { + if (input.isEmpty() || length == 0) { + return + } + val inputCursor = input.usePinned { aws_byte_cursor_from_array(it.addressOf(offset), length.convert()) } diff --git a/aws-crt-kotlin/native/test/aws/sdk/kotlin/crt/util/hashing/HashFunctionNativeTest.kt b/aws-crt-kotlin/native/test/aws/sdk/kotlin/crt/util/hashing/HashFunctionNativeTest.kt index a418112a..637d6439 100644 --- a/aws-crt-kotlin/native/test/aws/sdk/kotlin/crt/util/hashing/HashFunctionNativeTest.kt +++ b/aws-crt-kotlin/native/test/aws/sdk/kotlin/crt/util/hashing/HashFunctionNativeTest.kt @@ -2,6 +2,8 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ +package aws.sdk.kotlin.crt.util.hashing + import aws.sdk.kotlin.crt.util.encodeToHex import kotlin.test.Test import kotlin.test.assertEquals @@ -48,4 +50,21 @@ class HashFunctionTest { assertEquals(expected, hash.digest().encodeToHex()) } } + + @Test + fun testEmptyUpdate() { + // algorithm -> hash("") + val tests = listOf( + (Sha1() to "da39a3ee5e6b4b0d3255bfef95601890afd80709"), + (Sha256() to "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), + (Crc32() to "00000000"), + (Crc32c() to "00000000"), + ) + + tests.forEach { (hash, expected) -> + val data = "".encodeToByteArray() + hash.update(data, 0, 0) + assertEquals(expected, hash.digest().encodeToHex()) + } + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2bbbb1e7..a8ed2625 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin-version = "2.0.21" -aws-kotlin-repo-tools-version = "0.4.14-kn" +aws-kotlin-repo-tools-version = "0.4.16-kn" # libs crt-java-version = "0.31.3"