diff options
| author | ketsuban <ketsuban@192.168.3.110> | 2026-08-25 13:59:21 +0000 |
|---|---|---|
| committer | ketsuban <ketsuban@192.168.3.110> | 2026-08-25 13:59:21 +0000 |
| commit | 33dae5228c8e0b9645dc0330a36237eec2a1ed07 (patch) | |
| tree | bfb94b3c49881bcd584b630bc3880f1404ca3006 | |
| parent | 2e831207c1ae3674b62f8c0d3b80ea089ee3b2ae (diff) | |
| download | kukuri-33dae5228c8e0b9645dc0330a36237eec2a1ed07.tar.gz kukuri-33dae5228c8e0b9645dc0330a36237eec2a1ed07.tar.bz2 kukuri-33dae5228c8e0b9645dc0330a36237eec2a1ed07.zip | |
Ch.20a ContentStore seed: CID/block store + cryptography-kotlin 0.6.0 (optimal/jdk providers)
| -rw-r--r-- | build.gradle.kts | 3 | ||||
| -rw-r--r-- | gradle/libs.versions.toml | 4 | ||||
| -rw-r--r-- | src/commonMain/kotlin/jp/orgflow/contentstore/BlockStore.kt | 18 | ||||
| -rw-r--r-- | src/commonMain/kotlin/jp/orgflow/contentstore/Cid.kt | 21 | ||||
| -rw-r--r-- | src/commonMain/kotlin/jp/orgflow/contentstore/ContentAddresser.kt | 11 | ||||
| -rw-r--r-- | src/commonMain/kotlin/jp/orgflow/contentstore/ContentBlock.kt | 10 | ||||
| -rw-r--r-- | src/commonTest/kotlin/kukuri/ContentStoreTest.kt | 30 |
7 files changed, 97 insertions, 0 deletions
diff --git a/build.gradle.kts b/build.gradle.kts index adea8e3..132da86 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,8 @@ kotlin { implementation(libs.datetime) implementation(libs.koin.core) implementation(libs.okio) + implementation(libs.cryptography.core) + implementation(libs.cryptography.provider.optimal) } commonTest.dependencies { implementation(libs.kotlin.test) @@ -53,6 +55,7 @@ kotlin { androidMain.dependencies { implementation(libs.sqldelight.runtime) implementation(libs.datetime) + implementation(libs.cryptography.provider.jdk) } jvmTest.dependencies { implementation(libs.okio.fakefilesystem) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 628120e..f04273b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ koin = "4.0.0" sqldelight = "2.0.2" okio = "3.10.2" jgit = "7.7.1.202607240634-r" +cryptography = "0.6.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -20,6 +21,9 @@ sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sq sqldelight-driver-sqlite = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } okio-fakefilesystem = { module = "com.squareup.okio:okio-fakefilesystem", version.ref = "okio" } +cryptography-core = { module = "dev.whyoleg.cryptography:cryptography-core", version.ref = "cryptography" } +cryptography-provider-optimal = { module = "dev.whyoleg.cryptography:cryptography-provider-optimal", version.ref = "cryptography" } +cryptography-provider-jdk = { module = "dev.whyoleg.cryptography:cryptography-provider-jdk", version.ref = "cryptography" } jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "jgit" } [plugins] diff --git a/src/commonMain/kotlin/jp/orgflow/contentstore/BlockStore.kt b/src/commonMain/kotlin/jp/orgflow/contentstore/BlockStore.kt new file mode 100644 index 0000000..7d1e984 --- /dev/null +++ b/src/commonMain/kotlin/jp/orgflow/contentstore/BlockStore.kt @@ -0,0 +1,18 @@ +package jp.orgflow.contentstore + +interface BlockStore { + suspend fun put(block: ContentBlock) + suspend fun get(cid: Cid): ContentBlock? +} + +class MemoryBlockStore : BlockStore { + private val blocks = mutableMapOf<Cid, ContentBlock>() + + override suspend fun put(block: ContentBlock) { + blocks[block.cid] = block + } + + override suspend fun get(cid: Cid): ContentBlock? = blocks[cid] + + fun size(): Int = blocks.size +} diff --git a/src/commonMain/kotlin/jp/orgflow/contentstore/Cid.kt b/src/commonMain/kotlin/jp/orgflow/contentstore/Cid.kt new file mode 100644 index 0000000..2b12829 --- /dev/null +++ b/src/commonMain/kotlin/jp/orgflow/contentstore/Cid.kt @@ -0,0 +1,21 @@ +package jp.orgflow.contentstore + +data class Cid(val codec: String, val hashHex: String) { + override fun toString(): String = "$codec:$hashHex" + + companion object { + const val SHA256_CODEC = "sha256" + + fun fromSha256(hashBytes: ByteArray): Cid = + Cid(SHA256_CODEC, hex(hashBytes)) + + fun parse(raw: String): Cid { + val parts = raw.split(":") + require(parts.size == 2) { "invalid cid: $raw" } + return Cid(parts[0], parts[1]) + } + + private fun hex(bytes: ByteArray): String = + bytes.joinToString("") { (it.toInt() and 0xff).toString(16).padStart(2, '0') } + } +} diff --git a/src/commonMain/kotlin/jp/orgflow/contentstore/ContentAddresser.kt b/src/commonMain/kotlin/jp/orgflow/contentstore/ContentAddresser.kt new file mode 100644 index 0000000..656f104 --- /dev/null +++ b/src/commonMain/kotlin/jp/orgflow/contentstore/ContentAddresser.kt @@ -0,0 +1,11 @@ +package jp.orgflow.contentstore + +import dev.whyoleg.cryptography.CryptographyProvider +import dev.whyoleg.cryptography.algorithms.SHA256 + +object ContentAddresser { + private val sha256 by lazy { CryptographyProvider.Default.get(SHA256).hasher() } + + suspend fun cidOf(bytes: ByteArray): Cid = + Cid.fromSha256(sha256.hash(bytes)) +} diff --git a/src/commonMain/kotlin/jp/orgflow/contentstore/ContentBlock.kt b/src/commonMain/kotlin/jp/orgflow/contentstore/ContentBlock.kt new file mode 100644 index 0000000..85973d0 --- /dev/null +++ b/src/commonMain/kotlin/jp/orgflow/contentstore/ContentBlock.kt @@ -0,0 +1,10 @@ +package jp.orgflow.contentstore + +class ContentBlock(val cid: Cid, val bytes: ByteArray) { + override fun equals(other: Any?): Boolean = + other is ContentBlock && other.cid == cid && other.bytes.contentEquals(bytes) + + override fun hashCode(): Int = cid.hashCode() * 31 + bytes.contentHashCode() + + override fun toString(): String = "ContentBlock($cid, ${bytes.size}B)" +} diff --git a/src/commonTest/kotlin/kukuri/ContentStoreTest.kt b/src/commonTest/kotlin/kukuri/ContentStoreTest.kt new file mode 100644 index 0000000..f2656a8 --- /dev/null +++ b/src/commonTest/kotlin/kukuri/ContentStoreTest.kt @@ -0,0 +1,30 @@ +package jp.orgflow.contentstore + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ContentStoreTest { + @Test + fun cidMatchesSha256Vector() = kotlinx.coroutines.test.runTest { + val cid = ContentAddresser.cidOf("hello".encodeToByteArray()) + assertEquals( + "sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", + cid.toString() + ) + } + + @Test + fun blockStoreDeduplicatesByContent() = kotlinx.coroutines.test.runTest { + val store = MemoryBlockStore() + val b1 = ContentBlock(ContentAddresser.cidOf("same".encodeToByteArray()), "same".encodeToByteArray()) + val b2 = ContentBlock(ContentAddresser.cidOf("same".encodeToByteArray()), "same".encodeToByteArray()) + + store.put(b1) + store.put(b2) + + assertEquals(1, store.size()) + assertEquals(b1, store.get(b1.cid)) + assertNull(store.get(Cid.parse("sha256:00"))) + } +} |
