packages feed

hs-multiaddr (empty) → 0.1.0.0

raw patch · 14 files changed

+923/−0 lines, 14 filesdep +basedep +bytesdep +bytestringsetup-changed

Dependencies added: base, bytes, bytestring, cereal, either-unwrap, filepath, hs-multiaddr, hspec, io-streams, iproute, multihash, optparse-applicative, sandi, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright CMCDragonkai (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of CMCDragonkai nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,17 @@+Matrix Multi Address+=========================++Here is an experiment to combine the haskell implementation of Multiaddress with the Haskell implementation of Multihash.++Along the way, we must learn how stack works.++-- Multiaddr is a record wrapping a list of MultiaddrParts+-- a Multiaddr here is a typed Multiaddr within Haskell+-- in the outside world, there are 2 kinds of Multiaddr+-- a textual human readable version+-- a binary encoded version+-- this library exposes the typed Multiaddr+-- while allowing Multiaddr to be created from+-- human readable text or binary encoded string++There is any number of slashes.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hs-multiaddr.cabal view
@@ -0,0 +1,59 @@+name:                hs-multiaddr+version:             0.1.0.0+synopsis:            Multiaddr Library for LibP2P+description:         Multiaddr is self-describing way to represent addresses that support any standard network protocol.+                     Multiaddr has a binary packed format, and nice string representation.+                     It is possible to encapsulate addresses within each other.+homepage:            https://github.com/MatrixAI/haskell-multiaddr#readme+license:             BSD3+license-file:        LICENSE+author:              CMCDragonkai+maintainer:          roger.qiu@matrix.ai+copyright:           Copyright: (c) 2017 Roger Qiu+category:            Data+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Data.Multiaddr,+                       Data.MultiaddrPart,+                       Data.Multiaddr.IPFS,+                       Data.Multiaddr.VarInt+                       Data.Multiaddr.IPv4,+                       Data.Multiaddr.IPv6+                       Data.Multiaddr.Onion,+                       Data.Multiaddr.Port,+                       Data.Multiaddr.UnixPath+  build-depends:       base >= 4.7 && < 4.9+                     , bytestring+                     , text+                     , bytes+                     , sandi+                     , cereal+                     , iproute+                     , filepath+                     , multihash+                     , optparse-applicative >= 0.11 && < 0.12+                     , io-streams >= 1.2  && < 1.3+  default-language:    Haskell2010++test-suite haskell-multiaddr-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  build-depends:       base+                     , hs-multiaddr+                     , hspec+                     , iproute+                     , sandi+                     , bytestring+                     , multihash+                     , either-unwrap+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/MatrixAI/haskell-multiaddr
+ src/Data/Multiaddr.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DeriveDataTypeable #-}++module Data.Multiaddr+  (+    -- * As Multiaddr types+    Multiaddr (..),+    P.MultiaddrPart (..),+    -- * As a string+    toString,+    toMultiaddr,+    -- * As binary packed format+    encode,+    decode,+    -- * Utilities+    P.partHdrS,+    P.partHdrB,+    encapsulate,+    decapsulate,+    findFirstPart,+    findLastPart,+    findAllParts+  ) where++import qualified Text.ParserCombinators.ReadP as Parser+import qualified Data.ByteString as BSStrict+import qualified Data.MultiaddrPart as P++import GHC.Generics (Generic)+import Data.Typeable (Typeable)+import Data.String (IsString, fromString)+import Control.Applicative (many, some)+import Data.Bytes.Get (runGetS)+import Data.Bytes.Put (runPutS)+import Data.Serialize.Get (Get)+import Data.List (isPrefixOf, find, filter)++newtype Multiaddr = Multiaddr { parts :: [P.MultiaddrPart] }+  deriving (Show, Eq, Monoid, Generic, Typeable)++instance IsString Multiaddr where+  fromString s = either error id $ toMultiaddr s++toString :: Multiaddr -> String+toString (Multiaddr p) = concatMap P.toString p++toMultiaddr :: String -> Either String Multiaddr+toMultiaddr s =+  case Parser.readP_to_S (parse <* Parser.eof) s of+    (m, ""):_ -> Right m+    otherwise -> Left $ "Data.Multiaddr.toMultiaddr: invalid multiaddr " ++ show s++parse :: Parser.ReadP Multiaddr+parse = do+  multiParts <- some P.parse+  many $ Parser.char '/'+  return $ Multiaddr multiParts++encode :: Multiaddr -> BSStrict.ByteString+encode (Multiaddr p) = BSStrict.concat . map P.encode $ p++decode :: BSStrict.ByteString -> Either String Multiaddr+decode = runGetS $ parseB++parseB :: Get Multiaddr+parseB = Multiaddr <$> some P.parseB++-- first address encapsulates the second address+encapsulate :: Multiaddr -> Multiaddr -> Multiaddr+encapsulate m1 m2 = Multiaddr $ parts m1 ++ parts m2++-- first address is prefix removed from the second address+decapsulate :: Multiaddr -> Multiaddr -> Maybe Multiaddr+decapsulate (Multiaddr p1) (Multiaddr p2)+  | isPrefixOf p1 p2 = Just . Multiaddr . drop (length p1) $ p2+  | otherwise = Nothing++findFirstPart :: P.MultiaddrPart -> Multiaddr -> Maybe P.MultiaddrPart+findFirstPart p a = find (\p' -> P.partHdrB p' == pI) (parts a)+  where+    pI = P.partHdrB p++findLastPart :: P.MultiaddrPart -> Multiaddr -> Maybe P.MultiaddrPart+findLastPart p a = go (\p' -> P.partHdrB p' == pI) (parts a) Nothing+  where+    pI = P.partHdrB p+    go _ [] lastPart = lastPart+    go pred (p:ps) lastPart+      | pred p    = go pred ps $ Just p+      | otherwise = go pred ps lastPart++findAllParts :: P.MultiaddrPart -> Multiaddr -> [P.MultiaddrPart]+findAllParts p a = filter (\p' -> P.partHdrB p' == pI) (parts a)+  where+    pI = P.partHdrB p
+ src/Data/Multiaddr/IPFS.hs view
@@ -0,0 +1,40 @@+module Data.Multiaddr.IPFS+  (+    MHD.MultihashDigest (..),+    toString,+    parse,+    encode,+    parseB+  ) where++import qualified Text.ParserCombinators.ReadP as Parser+import qualified Data.ByteString as BSStrict+import qualified Data.ByteString.Lazy as BSLazy+import qualified Data.ByteString.Lazy.Char8 as BSLazyChar+import qualified Data.Multiaddr.VarInt as VarInt+import qualified Data.Multihash.Digest as MHD+import qualified Data.Multihash.Base as MHB++import Data.Serialize.Get (Get)++toString :: MHD.MultihashDigest -> String+toString h =+  BSLazyChar.unpack $+  MHB.encode MHB.Base58+  (BSLazy.fromStrict $ MHD.digest h)++parse :: Parser.ReadP MHD.MultihashDigest+parse = do+  multihashText <- Parser.munch1 (/= '/')+  case ((MHB.decode MHB.Base58) (BSLazyChar.pack multihashText)) of+    Right bs ->+      case MHD.decode (BSLazyChar.toStrict bs) of+        Right digest -> return digest+        otherwise -> Parser.pfail+    otherwise -> Parser.pfail++encode :: MHD.MultihashDigest -> BSStrict.ByteString+encode h = VarInt.encodeWith $ MHD.digest h++parseB :: Get MHD.MultihashDigest+parseB = VarInt.decodeSizeVar >>= (either fail return . MHD.decode)
+ src/Data/Multiaddr/IPv4.hs view
@@ -0,0 +1,29 @@+module Data.Multiaddr.IPv4+  (+    IPv4 (..),+    toString,+    parse,+    encode,+    parseB+  ) where++import qualified Text.ParserCombinators.ReadP as Parser+import qualified Data.ByteString as BSStrict+import qualified Data.Multiaddr.VarInt as VarInt++import Data.IP (IPv4 (..), fromIPv4, toIPv4)+import Data.Serialize.Get (Get)++toString :: IPv4 -> String+toString = show++parse :: Parser.ReadP IPv4+parse = Parser.readS_to_P reads++encode :: IPv4 -> BSStrict.ByteString+encode = BSStrict.pack . map fromIntegral . fromIPv4++parseB :: Get IPv4+parseB =+  fmap (toIPv4 . map fromIntegral . BSStrict.unpack) $+  VarInt.decodeSize 32
+ src/Data/Multiaddr/IPv6.hs view
@@ -0,0 +1,29 @@+module Data.Multiaddr.IPv6+  (+    IPv6 (..),+    toString,+    parse,+    encode,+    parseB+  ) where++import qualified Text.ParserCombinators.ReadP as Parser+import qualified Data.ByteString as BSStrict+import qualified Data.Multiaddr.VarInt as VarInt++import Data.IP (IPv6 (..), fromIPv6b, toIPv6b)+import Data.Serialize.Get (Get)++toString :: IPv6 -> String+toString = show++parse :: Parser.ReadP IPv6+parse = Parser.readS_to_P reads++encode :: IPv6 -> BSStrict.ByteString+encode = BSStrict.pack . map fromIntegral . fromIPv6b++parseB :: Get IPv6+parseB =+  fmap (toIPv6b . map fromIntegral . BSStrict.unpack) $+  VarInt.decodeSize 128
+ src/Data/Multiaddr/Onion.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE DeriveGeneric #-}++module Data.Multiaddr.Onion+  (+    Onion (..),+    toString,+    parse,+    encode,+    parseB+  ) where++import qualified Text.ParserCombinators.ReadP as Parser+import qualified Data.ByteString as BSStrict+import qualified Data.ByteString.Char8 as BSStrictChar+import qualified Codec.Binary.Base32 as Base32+import qualified Data.Multiaddr.VarInt as VarInt+import qualified Data.Multiaddr.Port as Port++import GHC.Generics (Generic)+import Data.Serialize.Get (Get)+import Data.Char (isAlphaNum, toUpper, toLower)++data Onion = Onion+  {+    onionHash :: {-# UNPACK #-} !BSStrict.ByteString,+    onionPort :: {-# UNPACK #-} !Port.Port+  }+  deriving (Show, Eq, Generic)++toString :: Onion -> String+toString (Onion h p) =+  hashToStr ++ ":" ++ portToStr+  where+    hashToStr = map toLower . BSStrictChar.unpack $ Base32.encode h+    portToStr = Port.toString p++parse :: Parser.ReadP Onion+parse = do+  onionHash <- fmap+    (BSStrictChar.pack . map toUpper)+    (Parser.count 16 $ Parser.satisfy isAlphaNum)+  case Base32.decode onionHash of+    Right onionHashDecoded -> do+      Parser.char ':'+      onionPort <- Port.parse+      if Port.port onionPort > 0+        then return $ Onion onionHashDecoded onionPort+        else Parser.pfail+    otherwise -> Parser.pfail++encode :: Onion -> BSStrict.ByteString+encode (Onion h p) = BSStrict.append h $ Port.encode p++parseB :: Get Onion+parseB = do+  h <- VarInt.decodeSize 80+  p <- Port.parseB+  return $ Onion h p
+ src/Data/Multiaddr/Port.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE DeriveGeneric #-}++module Data.Multiaddr.Port+  (+    Port (..),+    toString,+    parse,+    encode,+    parseB,+    toPort,+    fromPort+  ) where++import qualified Text.ParserCombinators.ReadP as Parser+import qualified Data.ByteString as BSStrict+import qualified Data.Multiaddr.VarInt as VarInt++import GHC.Generics (Generic)+import Data.Word (Word16)+import Data.Char (isDigit)+import Data.Bytes.Get (runGetS)+import Data.Bytes.Put (runPutS)+import Data.Bytes.Serial (serialize, deserialize)+import Data.Serialize.Get (Get)++newtype Port = Port+  {+    port :: Word16+  }+  deriving (Show, Eq, Ord, Generic)++toPort :: (Integral a, Eq a) => a -> Maybe Port+toPort n | n < 0 || n > 65535 = Nothing+         | otherwise          = Just $ Port (fromIntegral n)++fromPort :: Integral a => Port -> a+fromPort p = fromIntegral (port p)++toString :: Port -> String+toString (Port p) = show p++parse :: Parser.ReadP Port+parse = do+  port <- fmap read $ rangeParse (Parser.satisfy isDigit) 1 5 :: Parser.ReadP Int+  case toPort port of+    Just p -> return p+    Nothing -> Parser.pfail++rangeParse :: Read a => Parser.ReadP a -> Int -> Int -> Parser.ReadP [a]+rangeParse parse min max = foldr1 (Parser.<++) $+  fmap (\n -> Parser.count n $ parse) [max, max-1..min]++encode :: Port -> BSStrict.ByteString+encode (Port p) = (runPutS . serialize) $ p++parseB :: Get Port+parseB =+  VarInt.decodeSize 16 >>=+  (either fail (return . Port) . runGetS deserialize)
+ src/Data/Multiaddr/UnixPath.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE DeriveGeneric #-}++module Data.Multiaddr.UnixPath+  (+    UnixPath (..),+    toString,+    parse,+    encode,+    parseB+  ) where++import qualified Text.ParserCombinators.ReadP as Parser+import qualified Data.ByteString as BSStrict+import qualified Data.ByteString.Char8 as BSStrictChar+import qualified Data.Multiaddr.VarInt as VarInt++import GHC.Generics (Generic)+import System.FilePath (FilePath)+import Data.Serialize.Get (Get)++newtype UnixPath = UnixPath { path :: FilePath }+                 deriving (Show, Eq, Ord, Generic)++toString :: UnixPath -> String+toString (UnixPath p) = show p++parse :: Parser.ReadP UnixPath+parse = do+  path <- Parser.many1 Parser.get+  return $ UnixPath $ "/" ++ path++encode :: UnixPath -> BSStrict.ByteString+encode (UnixPath p) = VarInt.encodeWith $ BSStrictChar.pack p++parseB :: Get UnixPath+parseB = fmap (UnixPath . BSStrictChar.unpack) $ VarInt.decodeSizeVar+
+ src/Data/Multiaddr/VarInt.hs view
@@ -0,0 +1,33 @@+module Data.Multiaddr.VarInt+  (+    VarInt (..),+    encode,+    encodeWith,+    decode,+    decodeSize,+    decodeSizeVar+  ) where++import qualified Data.ByteString as BSStrict++import Data.Bytes.VarInt (VarInt (..))+import Data.Bytes.Get (getByteString)+import Data.Bytes.Put (runPutS)+import Data.Bytes.Serial (serialize, deserialize)+import Data.Serialize.Get (Get)++encode :: VarInt Int -> BSStrict.ByteString+encode = runPutS . serialize++encodeWith :: BSStrict.ByteString -> BSStrict.ByteString+encodeWith b =+  BSStrict.append ((encode . fromIntegral . BSStrict.length) b) b++decode :: Get (VarInt Int)+decode = deserialize++decodeSize :: Integral n => n -> Get BSStrict.ByteString+decodeSize n = getByteString $ fromIntegral $ div n 8++decodeSizeVar :: Get BSStrict.ByteString+decodeSizeVar = getByteString . fromIntegral =<< decode
+ src/Data/MultiaddrPart.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveDataTypeable #-}++module Data.MultiaddrPart+  (+    MultiaddrPart (..),+    toString,+    parse,+    encode,+    parseB,+    partHdrS,+    partHdrB+  ) where++import qualified Text.ParserCombinators.ReadP as Parser+import qualified Data.ByteString as BSStrict+import qualified Data.Multiaddr.IPFS as IPFS+import qualified Data.Multiaddr.VarInt as VarInt+import qualified Data.Multiaddr.IPv4 as IPv4+import qualified Data.Multiaddr.IPv6 as IPv6+import qualified Data.Multiaddr.Port as Port+import qualified Data.Multiaddr.Onion as Onion+import qualified Data.Multiaddr.UnixPath as UnixPath++import GHC.Generics (Generic)+import Data.Typeable (Typeable)+import Control.Applicative (some, (<|>))+import Control.Monad (unless)+import Data.Serialize.Get (Get)++data MultiaddrPart = IP4m   { ip4m   :: IPv4.IPv4 }+                   | IP6m   { ip6m   :: IPv6.IPv6 }+                   | TCPm   { tcpm   :: Port.Port }+                   | UDPm   { udpm   :: Port.Port }+                   | DCCPm  { dccpm  :: Port.Port }+                   | SCTPm  { sctpm  :: Port.Port }+                   | ONIONm { onionm :: Onion.Onion }+                   | IPFSm  { ipfsm :: IPFS.MultihashDigest }+                   | P2Pm   { p2pm  :: IPFS.MultihashDigest }+                   | UNIXm  { unixm  :: UnixPath.UnixPath }+                   | UTPm+                   | UDTm+                   | QUICm+                   | HTTPm+                   | HTTPSm+                   | WSm+                   | WSSm+                   deriving (Show, Eq, Generic, Typeable)++partHdrB :: MultiaddrPart -> VarInt.VarInt Int+partHdrB (IP4m _)   = 4+partHdrB (IP6m _)   = 41+partHdrB (TCPm _)   = 6+partHdrB (UDPm _)   = 17+partHdrB (DCCPm _)  = 33+partHdrB (SCTPm _)  = 132+partHdrB (ONIONm _) = 444+partHdrB (IPFSm _)  = 421+partHdrB (P2Pm _)   = 420+partHdrB (UNIXm _)  = 400+partHdrB UTPm       = 302+partHdrB UDTm       = 301+partHdrB QUICm      = 81+partHdrB HTTPm      = 480+partHdrB HTTPSm     = 443+partHdrB WSm        = 477+partHdrB WSSm       = 478++partHdrS :: MultiaddrPart -> String+partHdrS (IP4m _)   = "ip4"+partHdrS (IP6m _)   = "ip6"+partHdrS (TCPm _)   = "tcp"+partHdrS (UDPm _)   = "udp"+partHdrS (DCCPm _)  = "dccp"+partHdrS (SCTPm _)  = "sctp"+partHdrS (ONIONm _) = "onion"+partHdrS (IPFSm _)  = "ipfs"+partHdrS (P2Pm _)   = "p2p"+partHdrS (UNIXm _)  = "unix"+partHdrS UTPm       = "utp"+partHdrS UDTm       = "udt"+partHdrS QUICm      = "quic"+partHdrS HTTPm      = "http"+partHdrS HTTPSm     = "https"+partHdrS WSm        = "ws"+partHdrS WSSm       = "wss"++toString :: MultiaddrPart -> String+toString p = concat ["/", partHdrS p, go p]+  where+    go (IP4m i)   = "/" ++ IPv4.toString i+    go (IP6m i)   = "/" ++ IPv6.toString i+    go (TCPm p)   = "/" ++ Port.toString p+    go (UDPm p)   = "/" ++ Port.toString p+    go (DCCPm p)  = "/" ++ Port.toString p+    go (SCTPm p)  = "/" ++ Port.toString p+    go (ONIONm o) = "/" ++ Onion.toString o+    go (IPFSm h)  = "/" ++ IPFS.toString h+    go (P2Pm h)   = "/" ++ IPFS.toString h+    go (UNIXm p)  = "/" ++ UnixPath.toString p+    go UTPm       = ""+    go UDTm       = ""+    go QUICm      = ""+    go HTTPm      = ""+    go HTTPSm     = ""+    go WSm        = ""+    go WSSm       = ""++parse :: Parser.ReadP MultiaddrPart+parse =+  (IP4m <$> (parseHdr (IP4m undefined) *> parseSep *> IPv4.parse)) <|>+  (IP6m <$> (parseHdr (IP6m undefined) *> parseSep *> IPv6.parse)) <|>+  (TCPm <$> (parseHdr (TCPm undefined) *> parseSep *> Port.parse)) <|>+  (UDPm <$> (parseHdr (UDPm undefined) *> parseSep *> Port.parse)) <|>+  (DCCPm <$> (parseHdr (DCCPm undefined) *> parseSep *> Port.parse)) <|>+  (SCTPm <$> (parseHdr (SCTPm undefined) *> parseSep *> Port.parse)) <|>+  (ONIONm <$> (parseHdr (ONIONm undefined) *> parseSep *> Onion.parse)) <|>+  (IPFSm <$> (parseHdr (IPFSm undefined) *> parseSep *> IPFS.parse)) <|>+  (P2Pm <$> (parseHdr (P2Pm undefined) *> parseSep *> IPFS.parse)) <|>+  (UNIXm <$> (parseHdr (UNIXm undefined) *> parseSep *> UnixPath.parse)) <|>+  (parseHdr UTPm) <|>+  (parseHdr UDTm) <|>+  (parseHdr QUICm) <|>+  (parseHdr HTTPm) <|>+  (parseHdr HTTPSm) <|>+  (parseHdr WSm) <|>+  (parseHdr WSSm)+  where+    parseHdr c = c <$ (parseSep *> Parser.string (partHdrS c))+    parseSep = some $ Parser.char '/'++encode :: MultiaddrPart -> BSStrict.ByteString+encode p = BSStrict.append (VarInt.encode $ partHdrB p) (go p)+  where+    go (IP4m i)   = IPv4.encode i+    go (IP6m i)   = IPv6.encode i+    go (TCPm p)   = Port.encode p+    go (UDPm p)   = Port.encode p+    go (DCCPm p)  = Port.encode p+    go (SCTPm p)  = Port.encode p+    go (ONIONm o) = Onion.encode o+    go (IPFSm h)  = IPFS.encode h+    go (P2Pm h)   = IPFS.encode h+    go (UNIXm p)  = UnixPath.encode p+    go UTPm       = BSStrict.empty+    go UDTm       = BSStrict.empty+    go QUICm      = BSStrict.empty+    go HTTPm      = BSStrict.empty+    go HTTPSm     = BSStrict.empty+    go WSm        = BSStrict.empty+    go WSSm       = BSStrict.empty++parseB :: Get MultiaddrPart+parseB =+  (IP4m <$> (parseHdrB (IP4m undefined) *> IPv4.parseB)) <|>+  (IP6m <$> (parseHdrB (IP6m undefined) *> IPv6.parseB)) <|>+  (TCPm <$> (parseHdrB (TCPm undefined) *> Port.parseB)) <|>+  (UDPm <$> (parseHdrB (UDPm undefined) *> Port.parseB)) <|>+  (DCCPm <$> (parseHdrB (DCCPm undefined) *> Port.parseB)) <|>+  (SCTPm <$> (parseHdrB (SCTPm undefined) *> Port.parseB)) <|>+  (ONIONm <$> (parseHdrB (ONIONm undefined) *> Onion.parseB)) <|>+  (IPFSm <$> (parseHdrB (IPFSm undefined) *> IPFS.parseB)) <|>+  (P2Pm <$> (parseHdrB (P2Pm undefined) *> IPFS.parseB)) <|>+  (UNIXm <$> (parseHdrB (UNIXm undefined) *> UnixPath.parseB)) <|>+  (parseHdrB UTPm) <|>+  (parseHdrB UDTm) <|>+  (parseHdrB QUICm) <|>+  (parseHdrB HTTPm) <|>+  (parseHdrB HTTPSm) <|>+  (parseHdrB WSm) <|>+  (parseHdrB WSSm)+  where+    parseHdrB c = c <$ do+      i <- VarInt.decode+      unless (i == partHdrB c) $ fail "Wrong protocol index"
+ test/Spec.hs view
@@ -0,0 +1,259 @@+{-# LANGUAGE OverloadedStrings #-}++import qualified Data.ByteString as BSStrict+import qualified Data.ByteString.Char8 as BSStrictChar+import qualified Data.ByteString.Lazy.Char8 as BSLazyChar+import qualified Codec.Binary.Base32 as Base32+import qualified Data.Multihash.Digest as MHD+import qualified Data.Multihash.Base as MHB++import Data.IP (IPv4, IPv6, fromIPv4, fromIPv6, toIPv4, toIPv6)+import Data.Maybe (fromJust)+import Data.Char (toUpper)+import Data.Either.Unwrap (fromRight)+import Control.Exception (evaluate)+import Data.Either (isLeft, isRight)++import Test.Hspec+import Data.Multiaddr+import Data.Multiaddr.Onion (Onion (..))+import Data.Multiaddr.UnixPath (UnixPath (..))+import Data.Multiaddr.Port (toPort)++parseFail str = it ("should not parse `" ++ str ++ "`") $ do+  (toMultiaddr str) `shouldSatisfy` isLeft++parseSucceed str addr = it ("should parse `" ++ str ++ "`") $ do+  (toMultiaddr str) `shouldBe` Right addr++parseEqual str1 str2 = it ("should be equal `" ++ str1 ++ "` == `" ++ str2) $ do+  (toMultiaddr str1) `shouldBe` (toMultiaddr str2)++parseNotEqual str1 str2 = it ("should not be equal `" ++ str1 ++ "` /= `" ++ str2) $ do+  (toMultiaddr str1) `shouldNotBe` (toMultiaddr str2)++encodeSucceed str bs = it ("should encode `" ++ str ++ "` to `" ++ show bs ++ "`") $ do+  (fmap encode $ toMultiaddr str) `shouldBe` (Right $ BSStrict.pack bs)++decodeSucceed bs str = it ("should decode `" ++ show bs ++ "` to `" ++ str ++ "`") $ do+  (decode $ BSStrict.pack bs) `shouldBe` (toMultiaddr str)++encapsulateSucceed addr1 addr2 addr3 = it ("should encapsulate `" ++ toString addr2 ++ "` onto `" ++ toString addr1 ++ "`") $ do+  (encapsulate addr1 addr2) `shouldBe` addr3++decapsulateSucceed addr1 addr2 addr3 = it ("should decapsulate `" ++ toString addr1 ++ "` from `" ++ toString addr2 ++ "`") $ do+  (decapsulate addr1 addr2) `shouldBe` Just addr3++findFirstSucceed part1 part2 addr = it ("should find first `" ++ show part2 ++ "` in `" ++ toString addr) $ do+  (findFirstPart part1 addr) `shouldBe` Just part2++toIPFSm hash = IPFSm+              $ fromRight+              $ MHD.decode+              $ BSLazyChar.toStrict+              $ fromRight+              $ MHB.decode MHB.Base58+              $ BSLazyChar.pack hash++toP2Pm hash = P2Pm+              $ fromRight+              $ MHD.decode+              $ BSLazyChar.toStrict+              $ fromRight+              $ MHB.decode MHB.Base58+              $ BSLazyChar.pack hash++toONIONm hash port = ONIONm $+                       Onion+                       (fromRight $+                         Base32.decode $+                           BSStrictChar.pack $ map toUpper hash)+                       (fromJust $ toPort port)++main :: IO ()+main = hspec $ do+  describe "Multiaddr read" $ do+    parseFail "/ip4"+    parseFail "/ip4::1"+    parseFail "/ip4/fdpsofodsajfdoisa"+    parseFail "/ip6"+    parseFail "/udp"+    parseFail "/tcp"+    parseFail "/sctp"+    parseFail "/udp/65536"+    parseFail "/tcp/65536"+    parseFail "/quic/65536"+    parseFail "/onion/9imaq4ygg2iegci7:80"+    parseFail "/onion/aaimaq4ygg2iegci7:80"+    parseFail "/onion/timaq4ygg2iegci7:0"+    parseFail "/onion/timaq4ygg2iegci7:-1"+    parseFail "/onion/timaq4ygg2iegci7"+    parseFail "/onion/timaq4ygg2iegci@:666"+    parseFail "/udp/1234/sctp"+    parseFail "/udp/1234/udt/1234"+    parseFail "/udp/1234/utp/1234"+    parseFail "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa"+    parseFail "/ip4/127.0.0.1/udp"+    parseFail "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa"+    parseFail "/ip4/127.0.0.1/tcp"+    parseFail "/ip4/127.0.0.1/quic/1234"+    parseFail "/ip4/127.0.0.1/ipfs"+    parseFail "/ip4/127.0.0.1/ipfs/tcp"+    parseFail "/unix"+    parseFail "/ip4/1.2.3.4/tcp/80/unix"+    parseSucceed "/ip4/1.2.3.4" $ Multiaddr [(IP4m $ toIPv4 [1,2,3,4])]+    parseSucceed "/ip4/0.0.0.0" $ Multiaddr [(IP4m $ toIPv4 [0,0,0,0])]+    parseSucceed "/ip4/0.0.0.0//////" $ Multiaddr [(IP4m $ toIPv4 [0,0,0,0])]+    parseSucceed "/ip6/::1" $ Multiaddr [(IP6m $ toIPv6 [0,0,0,0,0,0,0,1])]+    parseSucceed "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21" $+      Multiaddr+        [+          (IP6m $ toIPv6+           [0x2601, 0x9, 0x4f81, 0x9700, 0x803e, 0xca65, 0x66e8, 0xc21])+        ]+    parseSucceed "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic" $+      Multiaddr+        [+          (IP6m $ toIPv6+           [0x2601, 0x9, 0x4f81, 0x9700, 0x803e, 0xca65, 0x66e8, 0xc21]),+          (UDPm $ fromJust $ toPort 1234),+          (QUICm)+        ]+    parseSucceed "/onion/timaq4ygg2iegci7:1234" $+      Multiaddr [(toONIONm "timaq4ygg2iegci7" 1234)]+    parseSucceed "/onion/timaq4ygg2iegci7:80/http" $+      Multiaddr+        [+          (toONIONm "timaq4ygg2iegci7" 80),+          (HTTPm)+        ]+    parseSucceed "/udp/0" $+      Multiaddr [(UDPm $ fromJust $ toPort 0)]+    parseSucceed "/tcp/0" $+      Multiaddr [(TCPm $ fromJust $ toPort 0)]+    parseSucceed "/sctp/0" $+      Multiaddr [(SCTPm $ fromJust $ toPort 0)]+    parseSucceed "/udp/1234" $+      Multiaddr [(UDPm $ fromJust $ toPort 1234)]+    parseSucceed "/tcp/1234" $+      Multiaddr [(TCPm $ fromJust $ toPort 1234)]+    parseSucceed "/tcp/1234////" $+      Multiaddr [(TCPm $ fromJust $ toPort 1234)]+    parseSucceed "/udp/65535" $+      Multiaddr [(UDPm $ fromJust $ toPort 65535)]+    parseSucceed "/tcp/65535" $+      Multiaddr [(TCPm $ fromJust $ toPort 65535)]+    parseSucceed "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC" $+      Multiaddr [(toIPFSm "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC")]+    parseSucceed "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC////" $+      Multiaddr [(toIPFSm "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC")]+    parseSucceed "/udp/1234/sctp/1234" $+      Multiaddr [(UDPm $ fromJust $ toPort 1234), (SCTPm $ fromJust $ toPort 1234)]+    parseSucceed "/udp/1234/udt" $+      Multiaddr [(UDPm $ fromJust $ toPort 1234), UDTm]+    parseSucceed "/udp/1234/utp" $+      Multiaddr [(UDPm $ fromJust $ toPort 1234), UTPm]+    parseSucceed "/tcp/1234/http" $+      Multiaddr [(TCPm $ fromJust $ toPort 1234), HTTPm]+    parseSucceed "/tcp/1234/https" $+      Multiaddr [(TCPm $ fromJust $ toPort 1234), HTTPSm]+    parseSucceed "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234" $+      Multiaddr+        [+          (toIPFSm "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"),+          (TCPm $ fromJust $ toPort 1234)+        ]+    parseSucceed "/ip4/127.0.0.1/udp/1234" $+      Multiaddr+        [+          (IP4m $ toIPv4 [127,0,0,1]),+          (UDPm $ fromJust $ toPort 1234)+        ]+    parseSucceed "/ip4/127.0.0.1/udp/0" $+      Multiaddr+        [+          (IP4m $ toIPv4 [127,0,0,1]),+          (UDPm $ fromJust $ toPort 0)+        ]+    parseSucceed "/ip4/127.0.0.1/tcp/1234" $+      Multiaddr+        [+          (IP4m $ toIPv4 [127,0,0,1]),+          (TCPm $ fromJust $ toPort 1234)+        ]+    parseSucceed "/ip4/127.0.0.1/tcp/1234/" $+      Multiaddr+        [+          (IP4m $ toIPv4 [127,0,0,1]),+          (TCPm $ fromJust $ toPort 1234)+        ]+    parseSucceed "/ip4/127.0.0.1/udp/1234/quic" $+      Multiaddr+        [+          (IP4m $ toIPv4 [127,0,0,1]),+          (UDPm $ fromJust $ toPort 1234),+          (QUICm)+        ]+    parseSucceed "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC" $+      Multiaddr+        [+          (IP4m $ toIPv4 [127,0,0,1]),+          (toIPFSm "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC")+        ]+    parseSucceed "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234" $+      Multiaddr+        [+          (IP4m $ toIPv4 [127,0,0,1]),+          (toIPFSm "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"),+          (TCPm $ fromJust $ toPort 1234)+        ]+    parseSucceed "/unix/a/b/c/d/e" $+      Multiaddr [(UNIXm $ UnixPath "/a/b/c/d/e")]+    parseSucceed "/unix/a/b/c/d/e/" $+      Multiaddr [(UNIXm $ UnixPath "/a/b/c/d/e")]+    parseSucceed "/unix/stdio///////" $+      Multiaddr [(UNIXm $ UnixPath "/stdio")]+    parseSucceed "/unix/stdio////a/b/c/d/e/f////" $+      Multiaddr [(UNIXm $ UnixPath "/stdio////a/b/c/d/e/f")]+    parseSucceed "/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f" $+      Multiaddr+        [+          (IP4m $ toIPv4 [1,2,3,4]),+          (TCPm $ fromJust $ toPort 80),+          (UNIXm $ UnixPath "/a/b/c/d/e/f")+        ]+    parseSucceed "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio" $+      Multiaddr+        [+          (IP4m $ toIPv4 [127,0,0,1]),+          (toIPFSm "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"),+          (TCPm $ fromJust $ toPort 1234),+          (UNIXm $ UnixPath "/stdio")+        ]+  describe "Multiaddr equal" $ do+    parseEqual "/ip4/127.0.0.1/tcp/1234" "/ip4/127.0.0.1/tcp/1234/"+    parseNotEqual "/ip4/127.0.0.1/udp/1234" "/ip4/127.0.0.1/tcp/1234"+  describe "Multiaddr encode" $ do+    encodeSucceed "/ip4/127.0.0.1/udp/1234" [4, 127, 0, 0, 1, 17, 4, 210]+    encodeSucceed "/ip4/127.0.0.1/tcp/4321" [4, 127, 0, 0, 1, 6, 16, 225]+    encodeSucceed "/ip4/127.0.0.1/udp/1234/ip4/127.0.0.1/tcp/4321" [4, 127, 0, 0, 1, 17, 4, 210, 4, 127, 0, 0, 1, 6, 16, 225]+  describe "Multiaddr decode" $ do+    decodeSucceed [4, 127, 0, 0, 1, 17, 4, 210] "/ip4/127.0.0.1/udp/1234"+    decodeSucceed [4, 127, 0, 0, 1, 6, 16, 225] "/ip4/127.0.0.1/tcp/4321"+    decodeSucceed [4, 127, 0, 0, 1, 17, 4, 210, 4, 127, 0, 0, 1, 6, 16, 225] "/ip4/127.0.0.1/udp/1234/ip4/127.0.0.1/tcp/4321"+    decodeSucceed [188, 3, 0, 16, 192, 67, 152, 49, 180, 130, 24, 72, 0, 80] "/onion/aaimaq4ygg2iegci:80"+  describe "Multiaddr encapsulate" $ do+    encapsulateSucceed+      ("/ip4/127.0.0.1" :: Multiaddr)+      ("/udp/1234" :: Multiaddr)+      ("/ip4/127.0.0.1/udp/1234" :: Multiaddr)+  describe "Multiaddr decapsulate" $ do+    decapsulateSucceed+      ("/udp/5678" :: Multiaddr)+      ("/udp/5678/ip4/127.0.0.1/udp/1234" :: Multiaddr)+      ("/ip4/127.0.0.1/udp/1234" :: Multiaddr)+  describe "Multiaddr find" $ do+    findFirstSucceed+      (IP4m undefined)+      (IP4m $ toIPv4 [127, 0, 0, 1])+      ("/ip4/127.0.0.1/utp/tcp/5555/udp/1234/utp/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP" :: Multiaddr)