summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorketsuban <ketsuban@192.168.3.110>2026-08-25 13:59:21 +0000
committerketsuban <ketsuban@192.168.3.110>2026-08-25 13:59:21 +0000
commit33dae5228c8e0b9645dc0330a36237eec2a1ed07 (patch)
treebfb94b3c49881bcd584b630bc3880f1404ca3006
parent2e831207c1ae3674b62f8c0d3b80ea089ee3b2ae (diff)
downloadkukuri-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.kts3
-rw-r--r--gradle/libs.versions.toml4
-rw-r--r--src/commonMain/kotlin/jp/orgflow/contentstore/BlockStore.kt18
-rw-r--r--src/commonMain/kotlin/jp/orgflow/contentstore/Cid.kt21
-rw-r--r--src/commonMain/kotlin/jp/orgflow/contentstore/ContentAddresser.kt11
-rw-r--r--src/commonMain/kotlin/jp/orgflow/contentstore/ContentBlock.kt10
-rw-r--r--src/commonTest/kotlin/kukuri/ContentStoreTest.kt30
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")))
+ }
+}