diff --git a/README.md b/README.md index 0c3dbd7..9bb4aa0 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,115 @@ See for an in-depth description of libp2p, please see: https://libp2p.io/ ## Getting started +First add the proper dependencies to your project: + +Kotlin DSL: + +```kotlin +repositories { + mavenCentral() +} + +dependencies { + implementation("org.erwinkok.result:libp2p-xxx:$latest") +} +``` + +`libp2p-core` is mandatory, other dependencies are optional depending on your needs. For example, if you need tcp +transport, include `libp2p-transport-tcp`, or if you want the mplex muxer include `libp2p-muxer-plex`. + +In your code, first create a host: + +```kotlin + val hostBuilder = host { + identity(localIdentity) + muxers { + mplex() + } + securityTransport { + noise() + } + transports { + tcp() + } + peerstore { + gcInterval = 1.hours + keyStore { + password = "APasswordThatIsAtLeast20CharactersLong" + dek { + salt = "W/SC6fnZfBIWdeAD3l+ClLpQtfICEtn+KYTUhfKq6d7l" + } + } + } + swarm { + dialTimeout = 10.minutes + listenAddresses { + multiAddress("/ip4/0.0.0.0/tcp/10333") + } + } + datastore(datastore) + } + + val host = hostBuilder.build(scope) + .getOrElse { + logger.error { "The following errors occurred while creating the host: ${errorMessage(it)}" } + return@runBlocking + } +``` + +The layout is hopefully clear: for example, the code above will use `tcp` as a transport, `noise` for security and +`mplex` as a muxer. + +Then you can add a handler: + +```kotlin + host.setStreamHandler(ProtocolId.of("/chat/1.0.0")) { + chatHandler(it) + } +``` +This means that if a peer connects and requests the `/chat/1.0.0` protocol, the corresponding handler will be called. + +To call a peer and open a new stream, use the following code: + +```kotlin + val stream = host.newStream(aPeerId, ProtocolId.of("/chat/1.0.0")) + .getOrElse { + logger.error { "Could not open chat stream with peer: ${errorMessage(it)}" } + return@runBlocking + } + chatHandler(stream) +``` + +This tries to connect to peer `aPeerId` and tries to open a new stream for protocol `/chat/1.0.0`. If it fails, it +returns if it succeeds it progresses to the chatHandler. + +See also the example application in `app`. + +To use this sample app, start the application. It will create a new random LocalIdentity (key-pair) and logs the adres +on which it listens on the output: + +```shell +[main] o.e.l.a.ApplicationKt$main$1: Local addresses the Host listens on: /ip4/0.0.0.0/tcp/10333/p2p/12D3KooWDfaEJxpmjbFLb9wUakCd6Lo6LRntaV3drb4EaYZRtYuY +``` + +In the libp2p-Go repository you can find `chat` in the examples directory. Then you can connect to the running instance +by using: + +```shell +./chat -d /ip4/0.0.0.0/tcp/10333/p2p/12D3KooWDfaEJxpmjbFLb9wUakCd6Lo6LRntaV3drb4EaYZRtYuY +``` + +On both sides it should mention that a connection is established. + ## Contact -If you want to contact me, please write an e-mail to: [erwin.kok@protonmail.com](mailto:erwin.kok@protonmail.com) +If you want to contact me, please write an e-mail to: [erwin-kok@gmx.com](mailto:erwin-kok@gmx.com) + +## Acknowledgements + +This work is largely based on the awesome libp2p-go implementation. This work would not have been possible without their +effort. Please consider giving kudos to the libp2p-go authors. +(See also [`ACKNOWLEDGEMENTS`](ACKNOWLEDGEMENTS.md)) ## License diff --git a/build-logic/src/main/kotlin/libp2p.common.gradle.kts b/build-logic/src/main/kotlin/libp2p.common.gradle.kts index 6bcf558..a34c95a 100644 --- a/build-logic/src/main/kotlin/libp2p.common.gradle.kts +++ b/build-logic/src/main/kotlin/libp2p.common.gradle.kts @@ -19,7 +19,7 @@ plugins { } group = "org.erwinkok.libp2p" -version = "0.1.0-SNAPSHOT" +version = "0.1.0" ktlint { verbose.set(true) diff --git a/build-logic/src/main/kotlin/libp2p.publish.gradle.kts b/build-logic/src/main/kotlin/libp2p.publish.gradle.kts index adf5a96..9f6edc7 100644 --- a/build-logic/src/main/kotlin/libp2p.publish.gradle.kts +++ b/build-logic/src/main/kotlin/libp2p.publish.gradle.kts @@ -37,7 +37,7 @@ publishing { developer { id.set("erwin-kok") name.set("Erwin Kok") - email.set("erwin.kok@protonmail.com") + email.set("erwin-kok@gmx.com") url.set("https://github.com/erwin-kok/") roles.set(listOf("owner", "developer")) } diff --git a/detekt-config.yml b/detekt-config.yml index ff30f25..8ff201a 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -1,27 +1,123 @@ -complexity: - active: false +console-reports: + active: true -naming: - active: false +output-reports: + active: true + exclude: + - 'TxtOutputReport' + - 'XmlOutputReport' + - 'SarifOutputReport' -empty-blocks: - active: false +build: + maxIssues: 0 + weights: + complexity: 2 + formatting: 1 + LongParameterList: 1 + comments: 1 -style: - active: false +processors: + active: true -potential-bugs: - ExplicitGarbageCollectionCall: - active: false +coroutines: + active: true + GlobalCoroutineUsage: + active: true + SuspendFunSwallowedCancellation: + active: true + SuspendFunWithCoroutineScopeReceiver: + active: true -performance: - SpreadOperator: +complexity: + active: true + ComplexCondition: + active: true + threshold: 6 + ComplexInterface: + active: true + threshold: 20 + includeStaticDeclarations: false + CyclomaticComplexMethod: + active: true + threshold: 35 + LabeledExpression: active: false - -exceptions: - TooGenericExceptionCaught: + LargeClass: + active: true + threshold: 1000 + MethodOverloading: active: false - TooGenericExceptionThrown: + threshold: 5 + NestedBlockDepth: + active: true + threshold: 7 + StringLiteralDuplication: active: false - ThrowingExceptionsWithoutMessageOrCause: + threshold: 2 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + thresholdInFiles: 50 + thresholdInClasses: 50 + thresholdInInterfaces: 20 + thresholdInObjects: 50 + thresholdInEnums: 50 + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: active: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + NotImplementedDeclaration: + active: true + ThrowingExceptionInMain: + active: true + +naming: + active: true + BooleanPropertyNaming: + active: true + +performance: + active: true + CouldBeSequence: + active: true + +potential-bugs: + active: true + CastNullableToNonNullableType: + active: true + CastToNullableType: + active: true + DontDowncastCollectionTypes: + active: true diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/base/HashCode.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/base/HashCode.kt index c76eebb..d514a2a 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/base/HashCode.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/base/HashCode.kt @@ -1,7 +1,7 @@ // Copyright (c) 2023 Erwin Kok. BSD-3-Clause license. See LICENSE file for more details. package org.erwinkok.libp2p.core.base -inline fun hashCodeOf(vararg values: Any?) = +fun hashCodeOf(vararg values: Any?) = values.fold(0) { acc, value -> (acc * 31) + value.hashCode() } diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/host/Host.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/host/Host.kt index 99f8680..9613ff6 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/host/Host.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/host/Host.kt @@ -22,7 +22,7 @@ interface Host : AwaitableClosable { val eventBus: EventBus fun addresses(): List - suspend fun connect(peerInfo: AddressInfo): Result + suspend fun connect(addressInfo: AddressInfo): Result fun setStreamHandler(protocolId: ProtocolId, handler: StreamHandler) fun setStreamHandlerMatch(protocolId: ProtocolId, matcher: (ProtocolId) -> Boolean, handler: StreamHandler) fun removeStreamHandler(protocolId: ProtocolId) diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/DialWorker.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/DialWorker.kt index b38688c..f150136 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/DialWorker.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/DialWorker.kt @@ -1,9 +1,12 @@ // Copyright (c) 2023 Erwin Kok. BSD-3-Clause license. See LICENSE file for more details. +@file:OptIn(DelicateCoroutinesApi::class) + package org.erwinkok.libp2p.core.network.swarm import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.Channel diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/Swarm.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/Swarm.kt index 8290262..5f195b1 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/Swarm.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/Swarm.kt @@ -6,13 +6,11 @@ import kotlinx.atomicfu.locks.ReentrantLock import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.Channel import mu.KotlinLogging import org.erwinkok.libp2p.core.base.AwaitableClosable import org.erwinkok.libp2p.core.event.EventBus import org.erwinkok.libp2p.core.host.PeerId import org.erwinkok.libp2p.core.host.builder.SwarmConfig -import org.erwinkok.libp2p.core.network.AddressDelay import org.erwinkok.libp2p.core.network.Connectedness import org.erwinkok.libp2p.core.network.Direction import org.erwinkok.libp2p.core.network.InetMultiaddress @@ -165,26 +163,6 @@ class Swarm private constructor( return peers.computeIfAbsent(peerId) { NetworkPeer(scope, peerId, this, resourceManager, multistreamMuxer) } } - // - // - // - - internal suspend fun bestAcceptableConnectionToPeer(peerId: PeerId): Result? { - return null - } - - internal suspend fun addressesForDial(peerId: PeerId): Result> { - return swarmDialer.addressesForDial(peerId) - } - - internal suspend fun dialRanker(addresses: List): List { - return dialRanker(addresses) - } - - internal suspend fun dialNextAddress(peerId: PeerId, address: InetMultiaddress, responseChannel: Channel): Result { - TODO("Not yet implemented") - } - internal fun addConnection(transportConnection: TransportConnection, direction: Direction): Result { val peerId = transportConnection.remoteIdentity.peerId return if (connectionGater != null) { diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/SwarmDialer.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/SwarmDialer.kt index 3193e49..60abd22 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/SwarmDialer.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/swarm/SwarmDialer.kt @@ -76,9 +76,7 @@ class SwarmDialer( resolvedByTransport.add(address) } } - val resolved = resolveAddresses(peerId, resolvedByTransport) - .getOrElse { return Err(it) } - val goodAddresses = IpUtil.unique(filterKnownUndiables(peerId, resolved)) + val goodAddresses = IpUtil.unique(filterKnownUndiables(peerId, resolvedByTransport)) if (goodAddresses.isEmpty()) { return Err("No good addresses for peer: $peerId") } @@ -86,11 +84,6 @@ class SwarmDialer( return Ok(goodAddresses) } - private suspend fun resolveAddresses(peerId: PeerId, addresses: List): Result> { - // TODO - return Ok(addresses) - } - private fun canDial(address: InetMultiaddress): Boolean { return swarmTransport.transportForDialing(address) .map { it.canDial(address) } @@ -103,10 +96,8 @@ class SwarmDialer( private fun filterKnownUndiables(peerId: PeerId, addresses: List): List { val ourAddresses = swarm.interfaceListenAddresses().getOr(listOf()) val diableAddresses = AddressUtil.filterAddresses(addresses, ::canDial) - val lowPriorityAddresses = filterLowPriorityAddresses(diableAddresses) - val nonBlackHoledAddresses = filterAddresses(lowPriorityAddresses) return AddressUtil.filterAddresses( - nonBlackHoledAddresses, + diableAddresses, listOf( { address -> ourAddresses.none { it == address } }, { address -> !IpUtil.isIp6LinkLocal(address) }, @@ -115,16 +106,6 @@ class SwarmDialer( ) } - private fun filterAddresses(addresses: List): List { - // TODO - return addresses - } - - private fun filterLowPriorityAddresses(addresses: List): List { - // TODO - return addresses - } - override fun close() { workers.values.forEach { it.close() } _context.cancel() diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/upgrader/Upgrader.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/upgrader/Upgrader.kt index fbffae5..67f8779 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/upgrader/Upgrader.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/network/upgrader/Upgrader.kt @@ -74,7 +74,7 @@ class Upgrader( connection.close() return Err("Gater rejected connection with peer ${secureMuxerInfo.secureConnection.remoteIdentity.peerId} and address ${connection.remoteAddress} with direction $direction") } - if (scope.peerScope() == null) { + if (scope.peerScope == null) { scope.setPeer(secureMuxerInfo.secureConnection.remoteIdentity.peerId) .getOrElse { val message = "resource manager blocked connection for peer. peer=${secureMuxerInfo.secureConnection.remoteIdentity.peerId}, address=${connection.remoteAddress}, direction $direction, error=${errorMessage(it)}" @@ -84,7 +84,7 @@ class Upgrader( } } val initiator = secureMuxerInfo.direction == Direction.DirOutbound - val muxedConnection = setupMuxer(secureMuxerInfo.secureConnection, initiator, scope.peerScope()) + val muxedConnection = setupMuxer(secureMuxerInfo.secureConnection, initiator, scope.peerScope) .getOrElse { secureMuxerInfo.secureConnection.close() return Err("failed to negotiate stream multiplexer: ${errorMessage(it)}") diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ConnectionManagementScope.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ConnectionManagementScope.kt index 725a15a..f541d58 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ConnectionManagementScope.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ConnectionManagementScope.kt @@ -6,6 +6,6 @@ import org.erwinkok.libp2p.core.host.PeerId import org.erwinkok.result.Result interface ConnectionManagementScope : ResourceScopeSpan, ConnectionScope { - fun peerScope(): PeerScope + val peerScope: PeerScope fun setPeer(peerId: PeerId): Result } diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/NullScope.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/NullScope.kt index 61288af..f9a0645 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/NullScope.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/NullScope.kt @@ -17,19 +17,25 @@ class NullScope : ConnectionScope, StreamManagementScope, StreamScope { + override val peerScope: PeerScope + get() = NullScope + override val name: String + get() = "" + override val protocol: ProtocolId + get() = ProtocolId.Null + override val peer: PeerId + get() = PeerId.Null + override fun reserveMemory(size: Int, prio: UByte): Result = Ok(Unit) override fun releaseMemory(size: Int) = Unit override fun statistic(): ScopeStatistic = ScopeStatistic() override fun beginSpan(): Result = Ok(NullScope) override fun done() = Unit - override fun name(): String = "" - override fun protocol(): ProtocolId = ProtocolId.Null - override fun peer(): PeerId = PeerId.Null - override fun peerScope(): PeerScope = NullScope override fun setPeer(peerId: PeerId): Result = Ok(Unit) override fun protocolScope(): ProtocolScope = NullScope override fun setProtocol(proto: ProtocolId): Result = Ok(Unit) override fun serviceScope(): ServiceScope = NullScope + override fun setService(srv: String): Result = Ok(Unit) companion object { diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/PeerScope.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/PeerScope.kt index 513ab51..40ed121 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/PeerScope.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/PeerScope.kt @@ -5,5 +5,5 @@ package org.erwinkok.libp2p.core.resourcemanager import org.erwinkok.libp2p.core.host.PeerId interface PeerScope : ResourceScope { - fun peer(): PeerId + val peer: PeerId } diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ProtocolScope.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ProtocolScope.kt index 9010225..1ceb456 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ProtocolScope.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ProtocolScope.kt @@ -5,5 +5,5 @@ package org.erwinkok.libp2p.core.resourcemanager import org.erwinkok.multiformat.multistream.ProtocolId interface ProtocolScope : ResourceScope { - fun protocol(): ProtocolId + val protocol: ProtocolId } diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ServiceScope.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ServiceScope.kt index 3eae002..5b40d93 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ServiceScope.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/ServiceScope.kt @@ -3,5 +3,5 @@ package org.erwinkok.libp2p.core.resourcemanager interface ServiceScope : ResourceScope { - fun name(): String + val name: String } diff --git a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/StreamManagementScope.kt b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/StreamManagementScope.kt index 2a18ad4..3b1d4be 100644 --- a/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/StreamManagementScope.kt +++ b/libp2p-core/src/main/kotlin/org/erwinkok/libp2p/core/resourcemanager/StreamManagementScope.kt @@ -6,8 +6,8 @@ import org.erwinkok.multiformat.multistream.ProtocolId import org.erwinkok.result.Result interface StreamManagementScope : StreamScope, ResourceScopeSpan { + val peerScope: PeerScope fun protocolScope(): ProtocolScope fun setProtocol(proto: ProtocolId): Result fun serviceScope(): ServiceScope - fun peerScope(): PeerScope }