Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions antd-kotlin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ All methods are `suspend` functions for use with Kotlin coroutines.
| **Files** | `filePut`, `fileGet`, `filePutPublic`, `fileGetPublic`, `fileCost`. Private variants return a caller-held DataMap (NOT stored on-network); public variants store the DataMap on-network at the returned address. All puts and `fileCost` accept a `PaymentMode` parameter. |
| **Pointers** | `pointerKeys`, `pointerCreate`, `pointerUpdate`, `pointerTransfer`, `pointerGet`, `pointerResolve`, `pointerFinality`, `pointerController`, `pointerPrepare`, `pointerFinalize`. See [Pointers](#pointers). |

### Upload results: `sha256`

`DataPutResult`, `DataPutPublicResult`, `FilePutResult`, `FilePutPublicResult`, `FinalizeUploadResult` and `FinalizeMerkleUploadResult` carry `sha256`: the plaintext's SHA-256 (hex); requires antd 0.17.0 or later, none on older daemons (`null`). It is the last constructor parameter, defaulting to `null`, so existing code that builds these results keeps compiling.

## Pointers

A pointer is a small, owner-signed, mutable reference. Its address is derived from the owner's key and never changes; what it points at (a chunk, such as a public file's data map, or another pointer) can be changed by the owner. The daemon holds the owner keys, loaded at startup with `antd --pointer-keys <file-or-dir>` (files in the format `ant pointer keygen` writes), so every write names the pointer by its address (`owner`), never by a key. An owner the daemon holds no key for is a `BadRequestException`. Addresses are 64 hex characters; the daemon accepts a `0x` prefix but always returns bare lowercase hex, so normalise before comparing. The pointer methods need antd 0.16.0 or later, the first release with pointers; both `AntdRestClient` and `AntdGrpcClient` implement them.
Expand Down Expand Up @@ -133,11 +137,11 @@ decoded.entries.forEach { println("${it.effectiveName}: ${it.exactSize} bytes")

| Method | Description |
|---|---|
| `buildManifest(entries, options = BuildManifestOptions())` | Build a manifest. Each `ManifestEntryInput` sets exactly one of `dataMap` (hex, embedded) or `address` (hex, public), plus an optional `path` and `size` (the size to record, as given). Both or neither throws `BadRequestException` (status 400) before anything is sent, with the daemon's own message listing every bad entry (`2 invalid entries: entries[0]: give data_map or address, not both; ...`). `BuildManifestOptions` has `name`, `torrentInfoHashes` and `embedRoots`, which defaults to `true`: the daemon resolves each shrunk DataMap and embeds its root when that is at most 64 KiB, so recipients skip the fetch. Returns `BuiltManifest`: `manifest` (the raw `.ant` bytes), `link` (null when it would not fit 1,500 bytes) and `entries` |
| `buildManifest(entries, options = BuildManifestOptions())` | Build a manifest. Each `ManifestEntryInput` sets exactly one of `dataMap` (hex, embedded) or `address` (hex, public), plus an optional `path`, `size` (the size to record, as given) and `sha256` (the file's plaintext SHA-256 as hex, passed through as given and checked by the daemon, which answers a malformed one with a 400 `BadRequestException`; requires antd 0.17.0 or later). Both or neither throws `BadRequestException` (status 400) before anything is sent, with the daemon's own message listing every bad entry (`2 invalid entries: entries[0]: give data_map or address, not both; ...`). `BuildManifestOptions` has `name`, `torrentInfoHashes` and `embedRoots`, which defaults to `true`: the daemon resolves each shrunk DataMap and embeds its root when that is at most 64 KiB, so recipients skip the fetch. Returns `BuiltManifest`: `manifest` (the raw `.ant` bytes), `link` (null when it would not fit 1,500 bytes) and `entries` |
| `decodeManifest(manifest, resolve = false)` | Decode `.ant` bytes. Returns `DecodedManifest`: `source` (`"manifest"`), `name`, `torrentInfoHashV1`, `torrentInfoHashV2` and `entries`. With `resolve`, the daemon fetches each entry's DataMap (not its content) to report `exactSize` and confirm it is on the network |
| `decodeManifestLink(link, resolve = false)` | Decode a manifest link, a file link or a bare 64-hex address. The last two come back as `source == "file_link"` with one public entry and no path |

Each entry is a `ManifestEntryInfo`: `effectiveName` (the path, or the content address when there is none), `path`, `size` (the recorded size), `kind` (`"embedded"` or `"public"`), `contentAddress` (hex; the public DataMap address, the same for private and public uploads of one file), `dataMap` (an embedded entry's DataMap in the hex form `dataGet` / `fileGet` accept; decodes only), `exactSize` and `error`.
Each entry is a `ManifestEntryInfo`: `effectiveName` (the path, or the content address when there is none), `path`, `size` (the recorded size), `kind` (`"embedded"` or `"public"`), `contentAddress` (hex; the public DataMap address, the same for private and public uploads of one file), `dataMap` (an embedded entry's DataMap in the hex form `dataGet` / `fileGet` accept; decodes only), `exactSize`, `error` and `sha256` (the plaintext's SHA-256 the manifest records for the entry, hex; null when it records none; requires antd 0.17.0 or later).

- **Bytes:** the methods take and return the raw `.ant` bytes on both transports. REST carries them as base64 inside the JSON, and the client converts.
- **Sizes** are `ULong?`. Unset is `null`, never `0`, on both transports: REST omits the field, and gRPC's proto3 `optional` is read by presence. A recorded `size` is only the creator's claim. `exactSize` comes from an embedded root DataMap or a resolving decode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class AntdGrpcClient internal constructor(
this.data = ByteString.copyFrom(data)
this.paymentMode = paymentMode.wire
})
DataPutPublicResult(address = resp.address, chunksStored = resp.chunksStored.toULong(), paymentModeUsed = resp.paymentModeUsed)
DataPutPublicResult(address = resp.address, chunksStored = resp.chunksStored.toULong(), paymentModeUsed = resp.paymentModeUsed, sha256 = resp.sha256.ifEmpty { null })
}

override suspend fun dataGetPublic(address: String): ByteArray = grpc {
Expand All @@ -165,7 +165,7 @@ class AntdGrpcClient internal constructor(
this.data = ByteString.copyFrom(data)
this.paymentMode = paymentMode.wire
})
DataPutResult(dataMap = resp.dataMap, chunksStored = resp.chunksStored.toULong(), paymentModeUsed = resp.paymentModeUsed)
DataPutResult(dataMap = resp.dataMap, chunksStored = resp.chunksStored.toULong(), paymentModeUsed = resp.paymentModeUsed, sha256 = resp.sha256.ifEmpty { null })
}

override suspend fun dataGet(dataMap: String): ByteArray = grpc {
Expand Down Expand Up @@ -347,6 +347,7 @@ class AntdGrpcClient internal constructor(
gasCostWei = resp.gasCostWei,
chunksStored = resp.chunksStored.toULong(),
paymentModeUsed = resp.paymentModeUsed,
sha256 = resp.sha256.ifEmpty { null },
)
}

Expand All @@ -366,6 +367,7 @@ class AntdGrpcClient internal constructor(
gasCostWei = resp.gasCostWei,
chunksStored = resp.chunksStored.toULong(),
paymentModeUsed = resp.paymentModeUsed,
sha256 = resp.sha256.ifEmpty { null },
)
}

Expand Down Expand Up @@ -536,6 +538,7 @@ class AntdGrpcClient internal constructor(
chunksStored = resp.chunksStored,
dataMap = resp.dataMap,
dataMapAddress = resp.dataMapAddress,
sha256 = resp.sha256.ifEmpty { null },
)
}

Expand All @@ -558,6 +561,7 @@ class AntdGrpcClient internal constructor(
chunksStored = resp.chunksStored,
dataMap = resp.dataMap,
dataMapAddress = resp.dataMapAddress,
sha256 = resp.sha256.ifEmpty { null },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ class AntdRestClient(
put("payment_mode", paymentMode.wire)
}.toString()
val resp = postJson<DataPutPublicDto>("/v1/data/public", body)
return DataPutPublicResult(resp.address, resp.chunksStored, resp.paymentModeUsed)
return DataPutPublicResult(resp.address, resp.chunksStored, resp.paymentModeUsed, resp.sha256?.ifEmpty { null })
}

override suspend fun dataGetPublic(address: String): ByteArray {
Expand All @@ -230,7 +230,7 @@ class AntdRestClient(
put("payment_mode", paymentMode.wire)
}.toString()
val resp = postJson<DataPutDto>("/v1/data", body)
return DataPutResult(resp.dataMap, resp.chunksStored, resp.paymentModeUsed)
return DataPutResult(resp.dataMap, resp.chunksStored, resp.paymentModeUsed, resp.sha256?.ifEmpty { null })
}

override suspend fun dataGet(dataMap: String): ByteArray {
Expand Down Expand Up @@ -384,6 +384,7 @@ class AntdRestClient(
gasCostWei = resp.gasCostWei,
chunksStored = resp.chunksStored,
paymentModeUsed = resp.paymentModeUsed,
sha256 = resp.sha256?.ifEmpty { null },
)
}

Expand All @@ -407,6 +408,7 @@ class AntdRestClient(
gasCostWei = resp.gasCostWei,
chunksStored = resp.chunksStored,
paymentModeUsed = resp.paymentModeUsed,
sha256 = resp.sha256?.ifEmpty { null },
)
}

Expand Down Expand Up @@ -556,6 +558,7 @@ class AntdRestClient(
chunksStored = resp.chunksStored,
dataMap = resp.dataMap,
dataMapAddress = resp.dataMapAddress,
sha256 = resp.sha256?.ifEmpty { null },
)
}

Expand All @@ -579,6 +582,7 @@ class AntdRestClient(
chunksStored = resp.chunksStored,
dataMap = resp.dataMap,
dataMapAddress = resp.dataMapAddress,
sha256 = resp.sha256?.ifEmpty { null },
)
}

Expand Down
16 changes: 16 additions & 0 deletions antd-kotlin/lib/src/main/kotlin/com/autonomi/sdk/Manifests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ data class ManifestEntryInput(
val dataMap: String? = null,
/** A public file's DataMap address (hex, 32 bytes), as `dataPutPublic` / `filePutPublic` return it. */
val address: String? = null,
/**
* The plaintext's SHA-256 to record (hex), passed through as given: the
* daemon validates it (a bad value is a 400). Null or empty: none is
* sent. Requires antd 0.17.0 or later.
*/
val sha256: String? = null,
)

/** The optional parts of an [IAntdClient.buildManifest] call. */
Expand Down Expand Up @@ -93,6 +99,11 @@ data class ManifestEntryInfo(
val exactSize: ULong? = null,
/** Why a resolving decode failed for this entry; null when it did not. */
val error: String? = null,
/**
* The plaintext's SHA-256 (lowercase hex) the manifest records for this
* entry; null when it records none. Requires antd 0.17.0 or later.
*/
val sha256: String? = null,
)

/** The result of [IAntdClient.buildManifest]. */
Expand Down Expand Up @@ -195,6 +206,7 @@ internal fun manifestBuildBody(entries: List<ManifestEntryInput>, options: Build
// A ULong is not a Number; JsonPrimitive(ULong) writes the exact u64.
e.size?.let { put("size", JsonPrimitive(it)) }
if (!e.dataMap.isNullOrEmpty()) put("data_map", e.dataMap) else put("address", e.address)
e.sha256?.takeIf { it.isNotEmpty() }?.let { put("sha256", it) }
}
}
}
Expand Down Expand Up @@ -228,6 +240,7 @@ internal data class ManifestEntryInfoDto(
@SerialName("data_map") val dataMap: String? = null,
@SerialName("exact_size") val exactSize: ULong? = null,
val error: String? = null,
val sha256: String? = null,
)

@Serializable
Expand Down Expand Up @@ -256,6 +269,7 @@ internal fun ManifestEntryInfoDto.toManifestEntryInfo() = ManifestEntryInfo(
dataMap = dataMap?.ifEmpty { null },
exactSize = exactSize,
error = error?.ifEmpty { null },
sha256 = sha256?.ifEmpty { null },
)

/** @throws InternalException if the daemon's `manifest` is not valid base64. */
Expand Down Expand Up @@ -294,6 +308,7 @@ internal fun buildManifestRequestOf(inputs: List<ManifestEntryInput>, options: B
e.path?.takeIf { it.isNotEmpty() }?.let { path = it }
e.size?.let { size = it.toLong() }
if (!e.dataMap.isNullOrEmpty()) dataMap = e.dataMap else address = e.address.orEmpty()
e.sha256?.takeIf { it.isNotEmpty() }?.let { sha256 = it }
}
}
}
Expand All @@ -307,6 +322,7 @@ internal fun Manifests.ManifestEntryInfo.toManifestEntryInfo() = ManifestEntryIn
dataMap = dataMap.ifEmpty { null },
exactSize = if (hasExactSize()) exactSize.toULong() else null,
error = error.ifEmpty { null },
sha256 = sha256.ifEmpty { null },
)

internal fun Manifests.BuildManifestResponse.toBuiltManifest() = BuiltManifest(
Expand Down
35 changes: 35 additions & 0 deletions antd-kotlin/lib/src/main/kotlin/com/autonomi/sdk/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ data class DataPutResult(
val dataMap: String,
val chunksStored: ULong = 0u,
val paymentModeUsed: String = "",
/**
* The plaintext's SHA-256 (lowercase hex, 64 characters). Requires antd
* 0.17.0 or later; null on older daemons.
*/
val sha256: String? = null,
)

/**
Expand All @@ -63,6 +68,11 @@ data class DataPutPublicResult(
val address: String,
val chunksStored: ULong = 0u,
val paymentModeUsed: String = "",
/**
* The plaintext's SHA-256 (lowercase hex, 64 characters). Requires antd
* 0.17.0 or later; null on older daemons.
*/
val sha256: String? = null,
)

/**
Expand All @@ -75,6 +85,11 @@ data class FilePutResult(
val gasCostWei: String,
val chunksStored: ULong,
val paymentModeUsed: String,
/**
* The plaintext's SHA-256 (lowercase hex, 64 characters). Requires antd
* 0.17.0 or later; null on older daemons.
*/
val sha256: String? = null,
)

/**
Expand All @@ -87,6 +102,11 @@ data class FilePutPublicResult(
val gasCostWei: String,
val chunksStored: ULong,
val paymentModeUsed: String,
/**
* The plaintext's SHA-256 (lowercase hex, 64 characters). Requires antd
* 0.17.0 or later; null on older daemons.
*/
val sha256: String? = null,
)

/** Wallet address response. */
Expand Down Expand Up @@ -140,6 +160,11 @@ data class FinalizeUploadResult(
@SerialName("chunks_stored") val chunksStored: Long,
@SerialName("data_map") val dataMap: String = "",
@SerialName("data_map_address") val dataMapAddress: String = "",
/**
* The plaintext's SHA-256 (lowercase hex, 64 characters). Requires antd
* 0.17.0 or later; null on older daemons.
*/
@SerialName("sha256") val sha256: String? = null,
)

/**
Expand All @@ -152,6 +177,11 @@ data class FinalizeMerkleUploadResult(
@SerialName("chunks_stored") val chunksStored: Long,
@SerialName("data_map") val dataMap: String = "",
@SerialName("data_map_address") val dataMapAddress: String = "",
/**
* The plaintext's SHA-256 (lowercase hex, 64 characters). Requires antd
* 0.17.0 or later; null on older daemons.
*/
@SerialName("sha256") val sha256: String? = null,
)

/**
Expand Down Expand Up @@ -572,13 +602,15 @@ internal data class DataPutPublicDto(
val address: String,
@SerialName("chunks_stored") val chunksStored: ULong = 0u,
@SerialName("payment_mode_used") val paymentModeUsed: String = "",
val sha256: String? = null,
)

@Serializable
internal data class DataPutDto(
@SerialName("data_map") val dataMap: String,
@SerialName("chunks_stored") val chunksStored: ULong = 0u,
@SerialName("payment_mode_used") val paymentModeUsed: String = "",
val sha256: String? = null,
)

@Serializable
Expand All @@ -588,6 +620,7 @@ internal data class FilePutPublicDto(
@SerialName("gas_cost_wei") val gasCostWei: String,
@SerialName("chunks_stored") val chunksStored: ULong,
@SerialName("payment_mode_used") val paymentModeUsed: String,
val sha256: String? = null,
)

@Serializable
Expand All @@ -597,6 +630,7 @@ internal data class FilePutDto(
@SerialName("gas_cost_wei") val gasCostWei: String,
@SerialName("chunks_stored") val chunksStored: ULong,
@SerialName("payment_mode_used") val paymentModeUsed: String,
val sha256: String? = null,
)

@Serializable
Expand Down Expand Up @@ -676,6 +710,7 @@ internal data class FinalizeUploadDto(
@SerialName("chunks_stored") val chunksStored: Long = 0,
@SerialName("data_map") val dataMap: String = "",
@SerialName("data_map_address") val dataMapAddress: String = "",
val sha256: String? = null,
)

@Serializable
Expand Down
Loading