Self-describing values for Future-proofing
This project provides Kotlin implementations of the Multiformats protocols.
Multiformats define self-describing data formats designed to remain interoperable across systems, languages, and decades. They are a foundational building block in systems such as IPFS and libp2p.
The goal of this project is to offer clear, explicit, and specification-faithful implementations of the core Multiformats standards, optimized for correctness and readability rather than convenience abstractions.
Specifications are defined at https://multiformats.io.
repositories {
mavenCentral()
}
dependencies {
implementation("org.erwinkok.multiformat:multiformat:$latest")
}- multiaddr — Self-describing network addresses
- multibase — Base encoding descriptors
- multicodec — Self-describing serialization codes
- multihash — Cryptographic hash identifiers
- multistream-select — Protocol negotiation
Additionally, this project implements:
- CID - Content Identifier
This project uses an explicit Result monad for error handling.
With few exceptions, public APIs return Result instead of throwing exceptions. This makes error propagation explicit, composable, and visible in the type system.
This is a deliberate design choice.
Exceptions are implicit and non-local: once execution enters a try block, it is no longer clear which operation caused control flow to jump to a catch clause. This complicates reasoning about resource ownership and cleanup.
By contrast, Result values make success and failure part of normal control flow.
Example:
val connection = createConnection()
.getOrElse {
log.error { "Could not create connection: ${errorMessage(it)}" }
return Err(it)
}
connection.write(...)Compared to exception-based control flow:
var connection: Connection? = null
try {
...
connection = createConnection()
...
methodThrowingException()
...
} catch (e: Exception) {
if (connection != null) {
connection.close()
}
}In the catch block, it is unclear whether the connection was successfully created or not.
Using Result makes this explicit:
val connection = createConnection()
.getOrElse {
log.error { "Could not create connection: ${errorMessage(it)}" }
return Err(it)
}
...
methodGeneratingError()
.onFailure {
connection.close()
return
}
...The control flow and resource lifetime are explicit and locally reasoned about.
This section provides a brief overview. For more comprehensive examples, see the test suite.
val addr1 = Multiaddress.fromString("/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234")
.getOrElse {
log.error { "Could not parse Multiaddress: ${errorMessage(it)}" }
return Err(it)
}
val ip6Addr = Multiaddress.fromString("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095").getOrThrow()
val tcpAddr = Multiaddress.fromString("/tcp/8000").getOrThrow()
val webAddr = Multiaddress.fromString("/ws").getOrThrow()
val actual1 = Multiaddress.fromString("/").expectNoErrors()
.encapsulate(ip6Addr).expectNoErrors()
.encapsulate(tcpAddr).expectNoErrors()
.encapsulate(webAddr).expectNoErrors()
.toString()val multibase = Multibase.encode("base16", "foobar".toByteArray()).getOrThrow()
val bytes = Multibase.decode("f666f6f626172").getOrThrow()val codec = Multicodec.nameToType("cidv2")val multihash = Multihash.fromBase58("QmPfjpVaf593UQJ9a5ECvdh2x17XuJYG5Yanv5UFnH3jPE")val selected = MultistreamMuxer.selectOneOf(setOf("/a", "/b", "/c"), connection)
.getOrElse {
log.error { "Error selecting protocol: ${errorMessage(it)}" }
return Err(it)
}This project has three submodules:
git submodule add https://github.com/multiformats/multicodec src/main/kotlin/org/erwinkok/multiformat/spec/multicodec
git submodule add https://github.com/multiformats/multibase src/main/kotlin/org/erwinkok/multiformat/spec/multibase
git submodule add https://github.com/multiformats/multihash src/main/kotlin/org/erwinkok/multiformat/spec/multihashThey are used for:
- Code generation
- Conformance testing
- Verifying behavior against the specifications
This project is licensed under the BSD-3-Clause license, see LICENSE file for more details.