From 847240717065c160db7ec849cc3f44e0078d8094 Mon Sep 17 00:00:00 2001 From: Sasikanth Miriyampalli Date: Thu, 11 Apr 2024 13:20:00 +0530 Subject: [PATCH] Add utility function to create name based uuid without namespace If the value is same, I don't want the generated UUID to be unique. This will generate the same output every time if the input is same. --- .../rss/reader/utils/uuid.android.kt | 41 +++++++++++++ .../dev/sasikanth/rss/reader/utils/uuid.kt | 43 +++++++++++++ .../sasikanth/rss/reader/utils/uuid.ios.kt | 61 +++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 shared/src/androidMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.android.kt create mode 100644 shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.kt create mode 100644 shared/src/iosMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.ios.kt diff --git a/shared/src/androidMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.android.kt b/shared/src/androidMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.android.kt new file mode 100644 index 000000000..7c0218c79 --- /dev/null +++ b/shared/src/androidMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.android.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Sasikanth Miriyampalli + * + * 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 + * + * http://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 dev.sasikanth.rss.reader.utils + +import com.benasher44.uuid.UuidHasher +import java.security.MessageDigest + +internal actual fun hasher(): UuidHasher { + return JvmHasher("SHA-1", 5) +} + +// Copied from: +// https://github.com/benasher44/uuid/blob/f3768dd19fdd58ac01711733923d7db5a433ac79/src/jvmMain/kotlin/namebased.kt#L33 +private class JvmHasher( + algorithmName: String, + override val version: Int, +) : UuidHasher { + private val digest = MessageDigest.getInstance(algorithmName) + + override fun update(input: ByteArray) { + digest.update(input) + } + + override fun digest(): ByteArray { + return digest.digest() + } +} diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.kt new file mode 100644 index 000000000..a5cccfd74 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Sasikanth Miriyampalli + * + * 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 + * + * http://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 dev.sasikanth.rss.reader.utils + +import com.benasher44.uuid.Uuid +import com.benasher44.uuid.UuidHasher +import com.benasher44.uuid.uuidOf +import kotlin.experimental.and +import kotlin.experimental.or + +// Number of bytes in a UUID +internal const val UUID_BYTES = 16 + +internal fun nameBasedUuidOf(value: String): Uuid { + val hasher = hasher() + hasher.update(value.encodeToByteArray()) + val hashedBytes = hasher.digest() + hashedBytes[6] = + hashedBytes[6] + .and(0b00001111) // clear the 4 most sig bits + .or(hasher.version.shl(4).toByte()) + hashedBytes[8] = + hashedBytes[8] + .and(0b00111111) // clear the 2 most sig bits + .or(-0b10000000) // set 2 most sig to 10 + return uuidOf(hashedBytes.copyOf(UUID_BYTES)) +} + +internal expect fun hasher(): UuidHasher diff --git a/shared/src/iosMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.ios.kt b/shared/src/iosMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.ios.kt new file mode 100644 index 000000000..b3133b5b6 --- /dev/null +++ b/shared/src/iosMain/kotlin/dev/sasikanth/rss/reader/utils/uuid.ios.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Sasikanth Miriyampalli + * + * 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 + * + * http://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 dev.sasikanth.rss.reader.utils + +import com.benasher44.uuid.UuidHasher +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.reinterpret +import kotlinx.cinterop.usePinned +import platform.CoreCrypto.CC_SHA1 +import platform.CoreCrypto.CC_SHA1_DIGEST_LENGTH + +internal actual fun hasher(): UuidHasher { + return AppleHasher(AppleHasher.Companion::sha1Digest, 5) +} + +// Copied from: +// https://github.com/benasher44/uuid/blob/f3768dd19fdd58ac01711733923d7db5a433ac79/src/appleMain/kotlin/namebased.kt#L40 +@OptIn(ExperimentalForeignApi::class) +private class AppleHasher( + private val digestFunc: (ByteArray) -> ByteArray, + override val version: Int, +) : UuidHasher { + private var data = ByteArray(0) + + override fun update(input: ByteArray) { + val prevLength = data.size + data = data.copyOf(data.size + input.size) + input.copyInto(data, prevLength) + } + + override fun digest(): ByteArray { + return digestFunc(data) + } + + companion object { + fun sha1Digest(data: ByteArray): ByteArray { + return ByteArray(CC_SHA1_DIGEST_LENGTH).also { bytes -> + bytes.usePinned { digestPin -> + data.usePinned { dataPin -> + CC_SHA1(dataPin.addressOf(0), data.size.toUInt(), digestPin.addressOf(0).reinterpret()) + } + } + } + } + } +}