From 22e7a9f3c3460c696b6a0fe7f1764b66f98f507e Mon Sep 17 00:00:00 2001 From: "dmitry.shakhtarin" Date: Tue, 19 Jun 2018 18:51:22 +0300 Subject: [PATCH] encapsulate and decapsulate methods --- .../scala/fluence/multiaddr/Multiaddr.scala | 76 +++++++------------ .../fluence/multiaddr/MultiaddrParser.scala | 72 ++++++++++++++++++ .../multiaddr/MultiaddrParseSpec.scala | 50 ++++++++++-- 3 files changed, 144 insertions(+), 54 deletions(-) create mode 100644 core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala diff --git a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala index 1344a3d..e5bce06 100644 --- a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala +++ b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala @@ -17,57 +17,37 @@ package fluence.multiaddr -import scala.annotation.tailrec +case class Multiaddr private(stringAddress: String, protocolsWithParameters: List[(Protocol, Option[String])]) { + override def toString: String = stringAddress -case class Multiaddr private(protocols: List[(Protocol, Option[String])]) + /** + * Wraps a given Multiaddr, returning the resulting joined Multiaddr. + * + * @return new joined Multiaddr + */ + def encapsulate(addr: Multiaddr): Either[Throwable, Multiaddr] = { + Multiaddr(stringAddress + addr.toString) + } + + /** + * Decapsulate unwraps Multiaddr up until the given Multiaddr is found. + * + * @return decapsulated Multiaddr + */ + def decapsulate(addr: Multiaddr): Either[Throwable, Multiaddr] = { + val strAddr = addr.toString + val lastIndex = stringAddress.lastIndexOf(strAddr) + if (lastIndex < 0) + Right(this.copy()) + else + Multiaddr(stringAddress.slice(0, lastIndex)) + } + +} object Multiaddr { - def apply(addr: String): Either[Throwable, Multiaddr] = { - if (!addr.startsWith("/")) { - Left(new IllegalArgumentException("Address must be started with '/'.")) - } else { - val parts = addr.stripPrefix("/").stripSuffix("/").split("/").toList - - if (parts.isEmpty) { - Left(new IllegalArgumentException("Address must be non-empty.")) - } else { - - parse(parts).map(protocols ⇒ new Multiaddr(protocols)) - } - } - } - - private def parse(list: List[String]): Either[Throwable, List[(Protocol, Option[String])]] = { - - @tailrec - def parseRec( - list: List[String], - res: Either[Throwable, List[(Protocol, Option[String])]] - ): Either[Throwable, List[(Protocol, Option[String])]] = { - list match { - case Nil ⇒ res - case head :: tail ⇒ - val protocolOp = Protocol.withNameOption(head) - - protocolOp match { - case None ⇒ - Left(new IllegalArgumentException(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 _ ⇒ - 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)))) - } - } - } - } - } - - parseRec(list, Right(List.empty)) + def apply(addr: String): Either[Throwable, Multiaddr] = MultiaddrParser.parse(addr).map { + case (trimmed, protocols) => new Multiaddr(trimmed, protocols) } } diff --git a/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala b/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala new file mode 100644 index 0000000..05d663a --- /dev/null +++ b/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala @@ -0,0 +1,72 @@ +/* + * 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 + +import scala.annotation.tailrec + +private[multiaddr] object MultiaddrParser { + + def parse(addr: String): Either[Throwable, (String, List[(Protocol, Option[String])])] = { + if (!addr.startsWith("/")) { + Left(new IllegalArgumentException("Address must be started with '/'.")) + } else { + val parts = addr.stripPrefix("/").stripSuffix("/").split("/").toList + + if (parts.isEmpty) { + Left(new IllegalArgumentException("Address must be non-empty.")) + } else { + + parsePrepared(parts).map(protocols ⇒ (addr.stripSuffix("/"), protocols)) + } + } + } + + private def parsePrepared(list: List[String]): Either[Throwable, List[(Protocol, Option[String])]] = { + + @tailrec + def parseRec( + list: List[String], + res: Either[Throwable, List[(Protocol, Option[String])]] + ): Either[Throwable, List[(Protocol, Option[String])]] = { + list match { + case Nil ⇒ res + 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'.")) + case Some(protocol) ⇒ + protocol.size match { + case 0 ⇒ parseRec(tail, res.map(els ⇒ els :+ (protocol, None))) + 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)))) + } + } + } + } + } + + parseRec(list, Right(List.empty)) + } +} diff --git a/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala b/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala index 479c50a..05603ba 100644 --- a/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala +++ b/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala @@ -31,13 +31,19 @@ class MultiaddrParseSpec extends WordSpec with Matchers { } "parse correct multiaddresses right" in { - val m1 = Multiaddr("/ip4/127.0.0.1/tcp/123") - m1.isRight shouldBe true - m1.right.get.protocols shouldBe List((IP4, Some("127.0.0.1")), (TCP, Some("123"))) + val addr1 = "/ip4/127.0.0.1/tcp/123" + 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 - val m2 = Multiaddr("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/udp/5000/https") - m2.isRight shouldBe true - m2.right.get.protocols shouldBe List((IP6, Some("2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095")), (UDP, Some("5000")), (HTTPS, None)) + 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 } "throw exception if there is no protocol" in { @@ -51,5 +57,37 @@ class MultiaddrParseSpec extends WordSpec with Matchers { m.isLeft shouldBe true m.left.get.getMessage shouldBe "There is no parameter for protocol with name 'tcp'." } + + "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 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 m3Either = m1.encapsulate(m2) + 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) + + m3.decapsulate(m2).right.get.stringAddress shouldBe addr1 + } + + "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 decapsulated = m1.decapsulate(Multiaddr("/sctp/5678").right.get).right.get + decapsulated.stringAddress shouldBe "/ip4/127.0.0.1/udp/1234" + } } }