From 0480ab37103bf21972b0a8daeaaacc93e15c413f Mon Sep 17 00:00:00 2001 From: "dmitry.shakhtarin" Date: Wed, 20 Jun 2018 17:45:26 +0300 Subject: [PATCH 1/3] add travis, add custom grpc protocol to list, small refactoring and bug fixes, add strict int type for parameters of TCP and UDP protocol --- .travis.yml | 19 +++++++ build.sbt | 1 + .../scala/fluence/multiaddr/Multiaddr.scala | 24 +++++---- .../fluence/multiaddr/MultiaddrParser.scala | 50 ++++++++++++++----- .../fluence/multiaddr/ProtoParameter.scala | 25 ++++++++++ .../scala/fluence/multiaddr/Protocol.scala | 3 ++ .../multiaddr/MultiaddrParseSpec.scala | 36 ++++++++----- 7 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 .travis.yml create mode 100644 core/src/main/scala/fluence/multiaddr/ProtoParameter.scala diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..571230d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +sudo: required + +language: scala +scala: + - 2.12.5 +jdk: + - oraclejdk8 + +# These directories are cached to S3 at the end of the build +cache: + directories: + - $HOME/.ivy2/cache + - $HOME/.sbt/boot + - $HOME/.sbt/launchers + +before_cache: + # Tricks to avoid unnecessary cache updates + - find $HOME/.sbt -name "*.lock" | xargs rm + - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm \ No newline at end of file diff --git a/build.sbt b/build.sbt index ef9e307..03250ef 100644 --- a/build.sbt +++ b/build.sbt @@ -39,6 +39,7 @@ lazy val `scala-multiaddr-core` = crossProject(JVMPlatform, JSPlatform) commons, libraryDependencies ++= Seq( "com.beachape" %%% "enumeratum" % "1.5.13", +// "org.typelevel" %%% "cats-core" % "1.1.0", "org.scalatest" %%% "scalatest" % "3.0.+" % Test ) ) diff --git a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala index e5bce06..57db5e4 100644 --- a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala +++ b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala @@ -17,16 +17,18 @@ package fluence.multiaddr -case class Multiaddr private(stringAddress: String, protocolsWithParameters: List[(Protocol, Option[String])]) { - override def toString: String = stringAddress +import fluence.multiaddr.Multiaddr.ErrorMessage + +case class Multiaddr private(address: String, protoParameters: List[ProtoParameter]) { + override def toString: String = address /** * Wraps a given Multiaddr, returning the resulting joined Multiaddr. * * @return new joined Multiaddr */ - def encapsulate(addr: Multiaddr): Either[Throwable, Multiaddr] = { - Multiaddr(stringAddress + addr.toString) + def encapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = { + Multiaddr(address + addr.toString) } /** @@ -34,20 +36,22 @@ case class Multiaddr private(stringAddress: String, protocolsWithParameters: Lis * * @return decapsulated Multiaddr */ - def decapsulate(addr: Multiaddr): Either[Throwable, Multiaddr] = { + def decapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = { val strAddr = addr.toString - val lastIndex = stringAddress.lastIndexOf(strAddr) + val lastIndex = address.lastIndexOf(strAddr) if (lastIndex < 0) - Right(this.copy()) + Right(this) else - Multiaddr(stringAddress.slice(0, lastIndex)) + Multiaddr(address.slice(0, lastIndex)) } } object Multiaddr { - def apply(addr: String): Either[Throwable, Multiaddr] = MultiaddrParser.parse(addr).map { - case (trimmed, protocols) => new Multiaddr(trimmed, protocols) + type ErrorMessage = String + + def apply(addr: String): Either[ErrorMessage, Multiaddr] = MultiaddrParser.parse(addr).map { + case (trimmed, protoParameters) => new Multiaddr(trimmed, protoParameters) } } diff --git a/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala b/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala index 05d663a..fc4a13e 100644 --- a/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala +++ b/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala @@ -17,18 +17,22 @@ package fluence.multiaddr +import fluence.multiaddr.Multiaddr.ErrorMessage +import fluence.multiaddr.Protocol._ + import scala.annotation.tailrec +import scala.util.Try private[multiaddr] object MultiaddrParser { - def parse(addr: String): Either[Throwable, (String, List[(Protocol, Option[String])])] = { + def parse(addr: String): Either[ErrorMessage, (String, List[ProtoParameter])] = { if (!addr.startsWith("/")) { - Left(new IllegalArgumentException("Address must be started with '/'.")) + Left("Address must be started with '/'.") } else { val parts = addr.stripPrefix("/").stripSuffix("/").split("/").toList if (parts.isEmpty) { - Left(new IllegalArgumentException("Address must be non-empty.")) + Left("Address must be non-empty.") } else { parsePrepared(parts).map(protocols ⇒ (addr.stripSuffix("/"), protocols)) @@ -36,31 +40,53 @@ private[multiaddr] object MultiaddrParser { } } - private def parsePrepared(list: List[String]): Either[Throwable, List[(Protocol, Option[String])]] = { + private def parseParameter(parameter: String, protocol: Protocol): Either[String, ProtoParameter] = { + protocol match { + case TCP | UDP ⇒ + Try(parameter.toInt).toEither.right + .map(n ⇒ IntProtoParameter(protocol, n)) + .left + .map(_ ⇒ s"Parameter for protocol $protocol must be a number.") + case _ ⇒ + Right(StringProtoParameter(protocol, parameter)) + } + } + + private def parsePrepared(list: List[String]): Either[ErrorMessage, List[ProtoParameter]] = { @tailrec def parseRec( list: List[String], - res: Either[Throwable, List[(Protocol, Option[String])]] - ): Either[Throwable, List[(Protocol, Option[String])]] = { + accum: Either[ErrorMessage, List[ProtoParameter]] + ): Either[ErrorMessage, List[ProtoParameter]] = { list match { - case Nil ⇒ res + case Nil ⇒ accum case head :: tail ⇒ //todo per-protocol validation val protocolOp = Protocol.withNameOption(head) protocolOp match { case None ⇒ - Left(new IllegalArgumentException(s"There is no protocol with name '$head'.")) + Left(s"There is no protocol with name '$head'.") case Some(protocol) ⇒ protocol.size match { - case 0 ⇒ parseRec(tail, res.map(els ⇒ els :+ (protocol, None))) + case 0 ⇒ + parseRec(tail, accum.map(els ⇒ els :+ EmptyProtoParameter(protocol))) case _ ⇒ tail match { case Nil ⇒ - Left(new IllegalArgumentException(s"There is no parameter for protocol with name '$head'.")) - case innerHead :: innerTail ⇒ - parseRec(innerTail, res.map(els ⇒ els :+ (protocol, Some(innerHead)))) + Left(s"There is no parameter for protocol with name '$head'.") + case parameter :: innerTail ⇒ + val partialResult = + for { + elements ← accum + parameter ← parseParameter(parameter, protocol) + } yield elements :+ parameter + + parseRec( + innerTail, + partialResult + ) } } } diff --git a/core/src/main/scala/fluence/multiaddr/ProtoParameter.scala b/core/src/main/scala/fluence/multiaddr/ProtoParameter.scala new file mode 100644 index 0000000..525bbec --- /dev/null +++ b/core/src/main/scala/fluence/multiaddr/ProtoParameter.scala @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 Fluence Labs Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package fluence.multiaddr + +sealed trait ProtoParameter + +//todo generalize parameters per protocol type +case class EmptyProtoParameter(protocol: Protocol) extends ProtoParameter +case class StringProtoParameter(protocol: Protocol, parameter: String) extends ProtoParameter +case class IntProtoParameter(protocol: Protocol, parameter: Int) extends ProtoParameter diff --git a/core/src/main/scala/fluence/multiaddr/Protocol.scala b/core/src/main/scala/fluence/multiaddr/Protocol.scala index 284fb62..ab422f6 100644 --- a/core/src/main/scala/fluence/multiaddr/Protocol.scala +++ b/core/src/main/scala/fluence/multiaddr/Protocol.scala @@ -61,4 +61,7 @@ object Protocol extends Enum[Protocol] { case object P2PWebrtcStar extends Protocol(275, 0, "p2p-webrtc-star") case object P2PWebrtcDirect extends Protocol(276, 0, "p2p-webrtc-direct") case object P2PCircuit extends Protocol(290, 0, "p2p-circuit") + + //custom + case object GRPC extends Protocol(1001, 0, "grpc") } diff --git a/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala b/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala index 05603ba..7511699 100644 --- a/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala +++ b/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala @@ -27,7 +27,7 @@ class MultiaddrParseSpec extends WordSpec with Matchers { "throw exception if there is no leading '/'" in { val m = Multiaddr("ip4/127.0.0.1/tcp/123") m.isLeft shouldBe true - m.left.get.getMessage shouldBe "Address must be started with '/'." + m.left.get shouldBe "Address must be started with '/'." } "parse correct multiaddresses right" in { @@ -35,27 +35,33 @@ class MultiaddrParseSpec extends WordSpec with Matchers { val m1Either = Multiaddr(addr1) m1Either.isRight shouldBe true val m1 = m1Either.right.get - m1.protocolsWithParameters shouldBe List((IP4, Some("127.0.0.1")), (TCP, Some("123"))) - m1.stringAddress shouldBe addr1 + + m1.protoParameters shouldBe List(StringProtoParameter(IP4, "127.0.0.1"), IntProtoParameter(TCP, 123)) + m1.address shouldBe addr1 val addr2 = "/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/udp/5000/https" val m2Either = Multiaddr(addr2) m2Either.isRight shouldBe true val m2 = m2Either.right.get - m2.protocolsWithParameters shouldBe List((IP6, Some("2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095")), (UDP, Some("5000")), (HTTPS, None)) - m2.stringAddress shouldBe addr2 + + m2.protoParameters shouldBe List( + StringProtoParameter(IP6, "2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095"), + IntProtoParameter(UDP, 5000), + EmptyProtoParameter(HTTPS) + ) + m2.address shouldBe addr2 } "throw exception if there is no protocol" in { val m = Multiaddr("/ip4/127.0.0.1/tc/123") m.isLeft shouldBe true - m.left.get.getMessage shouldBe "There is no protocol with name 'tc'." + m.left.get shouldBe "There is no protocol with name 'tc'." } "throw exception if there is no parameter in protocol with parameter" in { val m = Multiaddr("/ip4/127.0.0.1/tcp/") m.isLeft shouldBe true - m.left.get.getMessage shouldBe "There is no parameter for protocol with name 'tcp'." + m.left.get shouldBe "There is no parameter for protocol with name 'tcp'." } "encapsulate and decapsulate correct multiaddr" in { @@ -73,11 +79,17 @@ class MultiaddrParseSpec extends WordSpec with Matchers { m3Either.isRight shouldBe true val m3 = m3Either.right.get - val result = List((IP4, Some("127.0.0.1")), (TCP, Some("123")), (IP6, Some("2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095")), (UDP, Some("5000")), (HTTPS, None)) - m3.protocolsWithParameters shouldBe result - m3.stringAddress shouldBe (addr1 + addr2) + val result = List( + StringProtoParameter(IP4, "127.0.0.1"), + IntProtoParameter(TCP, 123), + StringProtoParameter(IP6, "2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095"), + IntProtoParameter(UDP, 5000), + EmptyProtoParameter(HTTPS) + ) + m3.protoParameters shouldBe result + m3.address shouldBe (addr1 + addr2) - m3.decapsulate(m2).right.get.stringAddress shouldBe addr1 + m3.decapsulate(m2).right.get.address shouldBe addr1 } "decapsulate correct multiaddr" in { @@ -87,7 +99,7 @@ class MultiaddrParseSpec extends WordSpec with Matchers { val m1 = m1Either.right.get val decapsulated = m1.decapsulate(Multiaddr("/sctp/5678").right.get).right.get - decapsulated.stringAddress shouldBe "/ip4/127.0.0.1/udp/1234" + decapsulated.address shouldBe "/ip4/127.0.0.1/udp/1234" } } } From 580b3f4fd73eeb42178ff3bae95cdcbf6b149e6f Mon Sep 17 00:00:00 2001 From: "dmitry.shakhtarin" Date: Wed, 20 Jun 2018 20:23:16 +0300 Subject: [PATCH 2/3] add readme and small refactoring --- README.md | 75 ++++++++++++++++++- build.sbt | 1 - .../scala/fluence/multiaddr/Multiaddr.scala | 16 ++-- .../fluence/multiaddr/MultiaddrParser.scala | 2 +- .../multiaddr/MultiaddrParseSpec.scala | 13 +--- 5 files changed, 87 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 16efae4..e4ee70a 100644 --- a/README.md +++ b/README.md @@ -1 +1,74 @@ -# scala-multiaddr \ No newline at end of file +# scala-multiaddr + +> [multiaddr](https://github.com/multiformats/multiaddr) implementation in scala + +Multiaddr is a standard way to represent addresses that: + +- Support any standard network protocols. +- Self-describe (include protocols). +- Have a binary packed format. +- Have a nice string representation. +- Encapsulate well. + +## Install + +```scala +// Bintray repo is used so far. Migration to Maven Central is planned +resolvers += Resolver.bintrayRepo("fluencelabs", "releases") + +libraryDependencies += "one.fluence" %%% "scala-multiaddr" % "0.0.1" +``` + +## Usage + +### Example + +#### Simple + +```scala +import fluence.multiaddr.Multiaddr + +val addr = "/ip4/127.0.0.1/tcp/1234" +// construct from a string (ErrorMessage return error message in ther parsing process) +val mEither: Either[ErrorMessage, Multiaddr] = Multiaddr(addr) +val m = mEither.right.get + +// true +m.address == "/ip4/127.0.0.1/tcp/1234" +``` + +#### Protocols + +```scala +// get the multiaddr protocol description objects +m.protoParameters + +//List( +// StringProtoParameter(IP4, "127.0.0.1"), +// IntProtoParameter(TCP, 1234), +// EmptyProtoParameter(HTTP) +//) +``` + +#### En/decapsulate + +```scala +m.encapsulate(Multiaddr.unsafe("/sctp/5678")) +// Multiaddr(/ip4/127.0.0.1/tcp/1234/sctp/5678,List(StringProtoParameter(IP4,127.0.0.1), IntProtoParameter(TCP,1234), IntProtoParameter(SCTP,5678))) +m.decapsulate("/tcp") // up to + inc last occurrence of subaddr +// Multiaddr(/ip4/127.0.0.1,List(StringProtoParameter(IP4,127.0.0.1))) +``` + +#### Tunneling + +Multiaddr allows expressing tunnels very nicely. + +```scala +val addr = Multiaddr.unsafe("/ip4/192.168.0.13/tcp/80") +val proxy = Multiaddr.unsafe("/ip4/10.20.30.40/tcp/443") +val addrOverProxy := proxy.encapsulate(m) +// /ip4/10.20.30.40/tcp/443/ip4/192.168.0.13/tcp/80 + +val proxyAgain = addrOverProxy.decapsulate(addr) +// /ip4/10.20.30.40/tcp/443 +``` \ No newline at end of file diff --git a/build.sbt b/build.sbt index 03250ef..ef9e307 100644 --- a/build.sbt +++ b/build.sbt @@ -39,7 +39,6 @@ lazy val `scala-multiaddr-core` = crossProject(JVMPlatform, JSPlatform) commons, libraryDependencies ++= Seq( "com.beachape" %%% "enumeratum" % "1.5.13", -// "org.typelevel" %%% "cats-core" % "1.1.0", "org.scalatest" %%% "scalatest" % "3.0.+" % Test ) ) diff --git a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala index 57db5e4..3e259bd 100644 --- a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala +++ b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala @@ -20,25 +20,23 @@ package fluence.multiaddr import fluence.multiaddr.Multiaddr.ErrorMessage case class Multiaddr private(address: String, protoParameters: List[ProtoParameter]) { - override def toString: String = address - /** * Wraps a given Multiaddr, returning the resulting joined Multiaddr. * * @return new joined Multiaddr */ - def encapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = { - Multiaddr(address + addr.toString) - } + def encapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = Multiaddr(address + addr.address) /** * Decapsulate unwraps Multiaddr up until the given Multiaddr is found. * * @return decapsulated Multiaddr */ - def decapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = { - val strAddr = addr.toString - val lastIndex = address.lastIndexOf(strAddr) + def decapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = decapsulate(addr.address) + + + def decapsulate(addr: String): Either[ErrorMessage, Multiaddr] = { + val lastIndex = address.lastIndexOf(addr) if (lastIndex < 0) Right(this) else @@ -54,4 +52,6 @@ object Multiaddr { def apply(addr: String): Either[ErrorMessage, Multiaddr] = MultiaddrParser.parse(addr).map { case (trimmed, protoParameters) => new Multiaddr(trimmed, protoParameters) } + + def unsafe(addr: String): Multiaddr = apply(addr).right.get } diff --git a/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala b/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala index fc4a13e..52e930a 100644 --- a/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala +++ b/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala @@ -42,7 +42,7 @@ private[multiaddr] object MultiaddrParser { private def parseParameter(parameter: String, protocol: Protocol): Either[String, ProtoParameter] = { protocol match { - case TCP | UDP ⇒ + case TCP | UDP | SCTP ⇒ Try(parameter.toInt).toEither.right .map(n ⇒ IntProtoParameter(protocol, n)) .left diff --git a/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala b/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala index 7511699..05ff139 100644 --- a/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala +++ b/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala @@ -17,6 +17,7 @@ package fluence.multiaddr +import fluence.multiaddr.Multiaddr.ErrorMessage import org.scalatest.{Matchers, WordSpec} class MultiaddrParseSpec extends WordSpec with Matchers { @@ -66,14 +67,10 @@ class MultiaddrParseSpec extends WordSpec with Matchers { "encapsulate and decapsulate correct multiaddr" in { val addr1 = "/ip4/127.0.0.1/tcp/123" - val m1Either = Multiaddr(addr1) - m1Either.isRight shouldBe true - val m1 = m1Either.right.get + val m1 = Multiaddr.unsafe(addr1) val addr2 = "/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/udp/5000/https" - val m2Either = Multiaddr(addr2) - m2Either.isRight shouldBe true - val m2 = m2Either.right.get + val m2 = Multiaddr.unsafe(addr2) val m3Either = m1.encapsulate(m2) m3Either.isRight shouldBe true @@ -94,9 +91,7 @@ class MultiaddrParseSpec extends WordSpec with Matchers { "decapsulate correct multiaddr" in { val addr1 = "/ip4/127.0.0.1/udp/1234/sctp/5678" - val m1Either = Multiaddr(addr1) - m1Either.isRight shouldBe true - val m1 = m1Either.right.get + val m1 = Multiaddr.unsafe(addr1) val decapsulated = m1.decapsulate(Multiaddr("/sctp/5678").right.get).right.get decapsulated.address shouldBe "/ip4/127.0.0.1/udp/1234" From fab58e2758c5d38195e9ab09650e05d1a5b8d57d Mon Sep 17 00:00:00 2001 From: "dmitry.shakhtarin" Date: Wed, 20 Jun 2018 20:46:53 +0300 Subject: [PATCH 3/3] add docs --- .../scala/fluence/multiaddr/Multiaddr.scala | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala index 3e259bd..6a83785 100644 --- a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala +++ b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala @@ -19,22 +19,55 @@ package fluence.multiaddr import fluence.multiaddr.Multiaddr.ErrorMessage +/** + * Multiaddress representation by string and list of protocols. + */ case class Multiaddr private(address: String, protoParameters: List[ProtoParameter]) { + /** - * Wraps a given Multiaddr, returning the resulting joined Multiaddr. + * Encapsulates a Multiaddr in another Multiaddr. * - * @return new joined Multiaddr + * Spec about encapsulate: + * https://github.com/multiformats/multiaddr#encapsulation-based-on-context + * + * val m = Multiaddr("/ip4/127.0.0.1/tcp/1234") + * println(m.encapsulate(Multiaddr.unsafe("/sctp/5678")).right.get.address) + * "/ip4/127.0.0.1/tcp/1234/sctp/5678" + * + * @param addr Multiaddr to add into this Multiaddr + * @return new Multiaddr */ def encapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = Multiaddr(address + addr.address) /** - * Decapsulate unwraps Multiaddr up until the given Multiaddr is found. + * Decapsulates a Multiaddr from another Multiaddr. + * + * Spec about encapsulate and decapsulate: + * https://github.com/multiformats/multiaddr#encapsulation-based-on-context + * + * val m = Multiaddr("/ip4/127.0.0.1/tcp/1234/sctp/5678") + * println(m.decapsulate(Multiaddr.unsafe("/sctp/5678")).right.get.address) + * "/ip4/127.0.0.1/tcp/1234" + * + * @param addr Multiaddr to remove from this Multiaddr * * @return decapsulated Multiaddr */ def decapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = decapsulate(addr.address) - + /** + * Possibility to decapsulate by part of address. + * + * Spec about encapsulate and decapsulate: + * https://github.com/multiformats/multiaddr#encapsulation-based-on-context + * + * val m = Multiaddr("/ip4/127.0.0.1/tcp/1234/sctp/5678") + * println(m.decapsulate("/sctp").right.get.address) + * "/ip4/127.0.0.1/tcp/1234" + * + * @param addr Multiaddr to remove from this Multiaddr + * @return decapsulated Multiaddr + */ def decapsulate(addr: String): Either[ErrorMessage, Multiaddr] = { val lastIndex = address.lastIndexOf(addr) if (lastIndex < 0) @@ -49,6 +82,9 @@ object Multiaddr { type ErrorMessage = String + /** + * Parse and validate multiaddr string. + */ def apply(addr: String): Either[ErrorMessage, Multiaddr] = MultiaddrParser.parse(addr).map { case (trimmed, protoParameters) => new Multiaddr(trimmed, protoParameters) }