packages feed

multiaddr (empty) → 0.1.0

raw patch · 7 files changed

+677/−0 lines, 7 filesdep +attoparsecdep +basedep +bytestringsetup-changed

Dependencies added: attoparsec, base, bytestring, cereal, errors, hashable, multiaddr, tasty, tasty-hunit, tasty-quickcheck, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, Micxjo Funkcio++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 Micxjo Funkcio 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,36 @@+# hs-multiaddr++[![Build Status](https://travis-ci.org/micxjo/hs-multiaddr.svg?branch=master)](https://travis-ci.org/micxjo/hs-multiaddr)++A [multiaddr](https://github.com/jbenet/multiaddr) implementation in Haskell. Multiaddr is a self-describing network address format supporting a variety of protocols, with both string and binary representations.++## Installation++Just clone and ``stack build``. Will be uploaded to Hackage soon!++## Usage++```haskell+import Network.Multiaddr++let Just somewhere = readMultiaddr "/ip4/8.8.8.8/tcp/80"+protocolNames somewhere -- ["ip4", "tcp"]++-- Encapsulation+-- Multiaddr is a monoid, `encapsulate` is just an alias for (<>)+let Just proxy = readMultiaddr "/ip6/::1/tcp/443"+let proxied = proxy `encapsulate` somewhere++toText proxied -- "/ip6/::1/tcp/443/ip4/8.8.8.8/tcp/80"+hasIPv6 proxied -- True++-- Grab an individual part of the address+parts proxied !! 2  -- "/ip4/8.8.8.8"++-- Encode into a ByteString+let bytes = encode proxied++-- Decode and get back the original!+let Just decoded = decode bytes+decoded == proxied -- True+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ multiaddr.cabal view
@@ -0,0 +1,53 @@+name: multiaddr+version: 0.1.0+cabal-version: >=1.10+build-type: Simple+license: BSD3+license-file: LICENSE+copyright: (c) 2016 Micxjo Funkcio+maintainer: Micxjo Funkcio <micxjo@fastmail.com>+homepage: http://github.com/micxjo/hs-multiaddr+synopsis: A network address format+description:+    Multiaddr is a self-describing network address format+    supporting a variety of protocols, with both string+    and binary representations.+category: Network+author: Micxjo Funkcio <micxjo@fastmail.com>+extra-source-files:+    README.md++source-repository head+    type: git+    location: https://github.com/micxjo/hs-multiaddr++library+    exposed-modules:+        Network.Multiaddr+    build-depends:+        base >=4.7 && <5,+        attoparsec >=0.13.0.1 && <0.14,+        text >=1.2.2.0 && <1.3,+        hashable >=1.2.4.0 && <1.3,+        errors >=2.1.2 && <2.2,+        cereal >=0.5.1.0 && <0.6,+        bytestring >=0.10.6.0 && <0.11+    default-language: Haskell2010+    hs-source-dirs: src+    other-modules:+        Data.Serialize.Varint+    ghc-options: -Wall -fwarn-incomplete-uni-patterns++test-suite multiaddr-test+    type: exitcode-stdio-1.0+    main-is: test.hs+    build-depends:+        base >=4.7 && <5,+        multiaddr >=0.1.0 && <0.2,+        tasty >=0.11.0.2 && <0.12,+        tasty-hunit >=0.9.2 && <0.10,+        tasty-quickcheck >=0.8.4 && <0.9,+        text >=1.2.2.0 && <1.3+    default-language: Haskell2010+    hs-source-dirs: test+    ghc-options: -threaded -rtsopts -with-rtsopts=-N
+ src/Data/Serialize/Varint.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Data.Serialize.Varint+       ( Varint(..)+       , getVarint+       , putVarint+       ) where++import Data.Bits+import GHC.Word (Word8)++import Data.Serialize (Serialize(..), Get, Put, putWord8, getWord8)++newtype Varint a = Varint a+                 deriving (Eq, Num, Ord, Real, Integral, Enum, Show, Bits)++getVarint :: (Num a, Bits a) => Word8 -> Get (Varint a)+getVarint n+  | testBit n 7 = do+      Varint m <- getWord8 >>= getVarint+      pure (shiftL m 7 .|. clearBit (fromIntegral n) 7)+  | otherwise = pure (fromIntegral n)+++putVarint :: (Integral a, Bits a) => a -> Put+putVarint n+  | n < 0x80 = putWord8 (fromIntegral n)+  | otherwise = do+      putWord8 (setBit (fromIntegral n) 7)+      putVarint (shiftR n 7)++instance (Bits a, Integral a) => Serialize (Varint a) where+  get = getWord8 >>= getVarint+  put = putVarint
+ src/Network/Multiaddr.hs view
@@ -0,0 +1,387 @@+{-# OPTIONS_HADDOCK prune #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-|+Module          : Network.Multiaddr+Description     : A network address format+Copyright       : (c) 2016 Micxjo Funkcio+License         : BSD3+Maintainer      : micxjo@fastmail.com+Stability       : Experimental++Multiaddr is a self-describing network address format supporting a+variety of protocols, with both string and binary representations.+-}+module Network.Multiaddr+       ( -- * Multiaddr type+         Multiaddr+         -- * Encoding / decoding+       , readMultiaddr+       , encode+       , decode+       , TextAddr(..)+         -- * Encapsulation+       , encapsulate+         -- * Query+       , parts+       , protocolNames+       , hasIPv4+       , hasIPv6+       , hasUDP+       , hasTCP+       , hasIPFS++         -- * AddrPart type+       , AddrPart(..)++         -- * IPv4 type+       , IPv4(..)+       , readIPv4+       , fromBytes++         -- * IPv6 type+       , IPv6(..)+       , readIPv6+       , fromPieces+       ) where++import           Control.Applicative ((<|>), many)+import           Control.Monad (guard)+import           Data.Bits+import           Data.List (intersperse, scanl', elemIndex)+import           Data.Maybe (fromJust)+import           Data.Monoid ((<>))+import           Data.Typeable (Typeable)+import           GHC.Generics (Generic)+import           GHC.Word (Word32, Word16, Word8, Word64)++import           Control.Error (hush)+import           Data.Attoparsec.Text hiding (take)+import           Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import           Data.Hashable (Hashable)+import           Data.Serialize (Serialize(..), Get, getBytes)+import qualified Data.Serialize as Cereal+import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import           Data.Text.Lazy (toStrict)+import           Data.Text.Lazy.Builder (Builder, toLazyText, singleton,+                                         fromText)+import qualified Data.Text.Lazy.Builder.Int as Builder++import           Data.Serialize.Varint++-- | An IPv4 address.+data IPv4 = IPv4 {-# UNPACK #-} !Word32+          deriving (Eq, Ord, Bounded, Generic, Typeable)++instance Hashable IPv4++instance Serialize IPv4 where+  get = IPv4 <$> get+  put (IPv4 w) = put w++-- | An IPv6 address.+data IPv6 = IPv6+            {-# UNPACK #-} !Word32+            {-# UNPACK #-} !Word32+            {-# UNPACK #-} !Word32+            {-# UNPACK #-} !Word32+          deriving (Eq, Ord, Bounded, Generic, Typeable)++instance Hashable IPv6++instance Serialize IPv6 where+  get = IPv6 <$> get <*> get <*> get <*> get+  put (IPv6 a b c d) = put a >> put b >> put c >> put d++asBytes :: IPv4 -> [Word8]+asBytes (IPv4 ip) =+  map (\n -> fromIntegral (ip `shiftR` n) .&. 0xFF) [24, 16, 8, 0]++asPieces :: IPv6 -> [Word16]+asPieces (IPv6 a b c d) =+  map fromIntegral [ a `shiftR` 16+                   , a .&. 0xFFFF+                   , b `shiftR` 16+                   , b .&. 0xFFFF+                   , c `shiftR` 16+                   , c .&. 0xFFFF+                   , d `shiftR` 16+                   , d .&. 0xFFFF+                   ]++fromBytes :: (Word8, Word8, Word8, Word8) -> IPv4+fromBytes (a, b, c, d) = IPv4 ( shiftL (fromIntegral a) 24+                          `xor` shiftL (fromIntegral b) 16+                          `xor` shiftL (fromIntegral c) 8+                          `xor` fromIntegral d)++fromPieces :: (Word16, Word16, Word16, Word16, Word16, Word16, Word16, Word16)+               -> IPv6+fromPieces (a, b, c, d, e, f, g, h) =+    IPv6 (word a b) (word c d) (word e f) (word g h)+  where word x y = (fromIntegral x `shiftL` 16) `xor` fromIntegral y++word8P :: Parser Word8+word8P = do+  w <- decimal :: Parser Integer+  guard (w >= 0 && w <= 255)+  pure (fromIntegral w)++ipv4P :: Parser IPv4+ipv4P = do+  a <- word8P <* char '.'+  b <- word8P <* char '.'+  c <- word8P <* char '.'+  d <- word8P+  pure (fromBytes (a, b, c, d))++-- | Try to read an IPv4 address (e.g. @"192.168.1.2"@)+readIPv4 :: Text -> Maybe IPv4+readIPv4 = hush . parseOnly (ipv4P <* endOfInput)++word16P :: Parser Word16+word16P = do+  w <- hexadecimal :: Parser Integer+  guard (w >= 0 && w <= 65535)+  pure (fromIntegral w)++ipv6P :: Parser IPv6+ipv6P = do+    heads <- sepBy' word16P (char ':')+    _ <- option ':' (char ':' *> char ':')+    tails <- sepBy' word16P (char ':')+    let len = length heads + length tails+    guard (len <= 8)+    let pieces = heads ++ replicate (8 - len) 0 ++ tails+    pure (fromPieces' pieces)+  where fromPieces' [a, b, c, d, e, f, g, h] =+          fromPieces (a, b, c, d, e, f, g, h)+        fromPieces' _ = error "unreachable"++-- | Try to read an IPv6 address+-- (e.g. @"2001:db8:85a3:8d3:1319:8a2e:370:7348"@)+readIPv6 :: Text -> Maybe IPv6+readIPv6 = hush . parseOnly (ipv6P <* endOfInput)++ipv4B :: IPv4 -> Builder+ipv4B = mconcat . intersperse (singleton '.') . map Builder.decimal . asBytes++{- All we need for ipv6B to pass LiquidHaskell -}+{-@ assert scanl' :: (b -> a -> b) -> b -> xs:[a]+                     -> {v : [b] | len(v) = 1 + len(xs) } @-}++ipv6B :: IPv6 -> Builder+ipv6B ip =+  if maxRun <= 1+     then trans pieces+     else trans start <> fromText "::" <> trans end+  where pieces = asPieces ip+        runs = tail (scanl' (\x y -> if y == 0 then x + 1 else 0) 0 pieces)+        maxRun = maximum runs+        maxInd = (fromJust (elemIndex maxRun runs)) - maxRun + 1+        start = take maxInd pieces+        end = drop (maxInd + maxRun) pieces+        trans = mconcat . intersperse (singleton ':') . map Builder.hexadecimal++-- | Render an address to its standard text representation.+class TextAddr a where+  toText :: a -> Text++instance TextAddr IPv4 where+  toText = toStrict . toLazyText . ipv4B++-- | RFC 5952 Compliant+instance TextAddr IPv6 where+  toText = toStrict . toLazyText . ipv6B++instance Show IPv4 where+  show = T.unpack . toText++instance Show IPv6 where+  show = T.unpack . toText++-- | An individual component of a multiaddr.+data AddrPart = IPv4Part !IPv4+              | IPv6Part !IPv6+              | UDPPart !Word16+              | TCPPart !Word16+              | IPFSPart !ByteString+              | UDTPart+              | UTPPart+              deriving (Eq, Generic, Typeable)++instance Hashable AddrPart++instance Serialize AddrPart where+  put (IPv4Part ip) = put (4 :: Varint Word32) >> put ip+  put (IPv6Part ip) = put (41 :: Varint Word32) >> put ip+  put (UDPPart port) = put (17 :: Varint Word32) >> put port+  put (TCPPart port) = put (6 :: Varint Word32) >> put port+  put (IPFSPart addr) = do+    put (421 :: Varint Word32)+    put (fromIntegral (BS.length addr) :: Varint Word64)+    mapM_ put (BS.unpack addr)+  put UDTPart = put (301 :: Varint Word32)+  put UTPPart = put (302 :: Varint Word32)++  get = do+    code <- get :: Get (Varint Word32)+    case code of+      4 -> IPv4Part <$> get+      6 -> TCPPart <$> get+      17 -> UDPPart <$> get+      41 -> IPv6Part <$> get+      421 -> do+        len <- get :: Get (Varint Word64)+        IPFSPart <$> getBytes (fromIntegral len)+      301 -> pure UDTPart+      302 -> pure UTPPart+      _ -> fail "invalid multiaddr code"++-- | A network address.+newtype Multiaddr = Multiaddr { _parts :: [AddrPart] }+                  deriving (Eq, Monoid, Generic, Typeable)++instance Hashable Multiaddr++instance Serialize Multiaddr where+  get = Multiaddr <$> many get++  put (Multiaddr ps) = mapM_ put ps++-- | Get the individual parts of the multiaddr, in order+-- (e.g. @["\/ip4\/8.8.8.8", "\/tcp\/80"]@)+parts :: Multiaddr -> [AddrPart]+parts = _parts++ipv4PartP :: Parser AddrPart+ipv4PartP = IPv4Part <$> (string "/ip4/" *> ipv4P)++ipv6PartP :: Parser AddrPart+ipv6PartP = IPv6Part <$> (string "/ip6/" *> ipv6P)++udpPartP :: Parser AddrPart+udpPartP = do+  _ <- string "/udp/"+  port <- decimal :: Parser Integer+  guard (port >= 0 && port <= 65535)+  pure (UDPPart (fromIntegral port))++udtPartP :: Parser AddrPart+udtPartP = string "/udt" >> pure UDTPart++utpPartP :: Parser AddrPart+utpPartP = string "/utp" >> pure UTPPart++udpAddrP :: Parser Multiaddr+udpAddrP = do+  udp <- udpPartP+  sub <- option Nothing (Just <$> (udtPartP <|> utpPartP))+  pure $ case sub of+    Nothing -> Multiaddr [udp]+    Just sub' -> Multiaddr [udp, sub']++tcpPartP :: Parser AddrPart+tcpPartP = do+  _ <- string "/tcp/"+  port <- decimal :: Parser Integer+  guard (port >= 0 && port <= 65535)+  pure (TCPPart (fromIntegral port))++ipfsPartP :: Parser AddrPart+ipfsPartP = do+  _ <- string "/ipfs/"+  addr <- takeTill (== '/')+  pure (IPFSPart (T.encodeUtf8 addr))++portAddrP :: Parser Multiaddr+portAddrP = udpAddrP <|> ((\t -> Multiaddr [t]) <$> tcpPartP)++addrP :: Parser Multiaddr+addrP = do+  ipPart <- ipv4PartP <|> ipv6PartP <|> ipfsPartP+  port <- option Nothing (Just <$> portAddrP)+  pure $ case port of+    Nothing -> Multiaddr [ipPart]+    Just port' -> Multiaddr [ipPart] <> port'++multiaddrP :: Parser Multiaddr+multiaddrP = mconcat <$> many' addrP++-- | Try to read a multiaddr in the standard text format+-- (e.g. @"\/ip4\/8.8.8.8\/tcp\/80"@)+readMultiaddr :: Text -> Maybe Multiaddr+readMultiaddr = hush . parseOnly (multiaddrP <* endOfInput)++addrPartB :: AddrPart -> Builder+addrPartB (IPv4Part ip) = fromText "/ip4/" <> ipv4B ip+addrPartB (IPv6Part ip) = fromText "/ip6/" <> ipv6B ip+addrPartB (UDPPart port) = fromText "/udp/" <> Builder.decimal port+addrPartB (TCPPart port) = fromText "/tcp/" <> Builder.decimal port+addrPartB (IPFSPart addr) = fromText "/ipfs/" <> fromText (T.decodeUtf8 addr)+addrPartB UDTPart = fromText "/udt"+addrPartB UTPPart = fromText "/utp"++instance TextAddr AddrPart where+  toText = toStrict . toLazyText . addrPartB++instance Show AddrPart where+  show = T.unpack . toText++multiaddrB :: Multiaddr -> Builder+multiaddrB (Multiaddr ps) = mconcat (map addrPartB ps)++instance TextAddr Multiaddr where+  toText = toStrict . toLazyText . multiaddrB++instance Show Multiaddr where+  show = T.unpack . toText++-- | Encode a multiaddr using the standard binary represenation.+encode :: Multiaddr -> ByteString+encode = Cereal.encode++-- | Try to decode a binary-encoded multiaddr.+decode :: ByteString -> Maybe Multiaddr+decode = hush . Cereal.decode++-- | Does the multiaddr contain an IPv4 part?+hasIPv4 :: Multiaddr -> Bool+hasIPv4 (Multiaddr ps) = any (\case IPv4Part _ -> True; _ -> False) ps++-- | Does the multiaddr contain an IPv6 part?+hasIPv6 :: Multiaddr -> Bool+hasIPv6 (Multiaddr ps) = any (\case IPv6Part _ -> True; _ -> False) ps++-- | Does the multiaddr contain a TCP part?+hasTCP :: Multiaddr -> Bool+hasTCP (Multiaddr ps) = any (\case TCPPart _ -> True; _ -> False) ps++-- | Does the multiaddr contain a UDP part?+hasUDP :: Multiaddr -> Bool+hasUDP (Multiaddr ps) = any (\case UDPPart _ -> True; _ -> False) ps++-- | Does the multiaddr contain an IPFS part?+hasIPFS :: Multiaddr -> Bool+hasIPFS (Multiaddr ps) = any (\case IPFSPart _ -> True; _ -> False) ps++-- | Append two multiaddrs (alias for @(<>)@)+encapsulate :: Multiaddr -> Multiaddr -> Multiaddr+encapsulate = mappend++-- | Get an ordered list of the protocols specified by the multiaddr.+-- Protocols can appear more than once if they are repeated in the multiaddr.+protocolNames :: Multiaddr -> [Text]+protocolNames (Multiaddr ms) = map protoName ms+  where protoName (IPv4Part _) = "ip4"+        protoName (IPv6Part _) = "ip6"+        protoName (UDPPart _) = "udp"+        protoName (TCPPart _) = "tcp"+        protoName (IPFSPart _) = "ipfs"+        protoName UDTPart = "udt"+        protoName UTPPart = "utp"
+ test/test.hs view
@@ -0,0 +1,136 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE OverloadedStrings #-}+import           Data.Maybe (fromJust)++import           Test.Tasty+import           Test.Tasty.HUnit+import qualified Test.Tasty.QuickCheck as QC++import           Network.Multiaddr++instance QC.Arbitrary IPv4 where+  arbitrary = fromBytes <$> QC.arbitrary++instance QC.Arbitrary IPv6 where+  arbitrary = do+    (a, b, c, d) <- QC.arbitrary+    (e, f, g, h) <- QC.arbitrary+    pure (fromPieces (a, b, c, d, e, f, g, h))++ipv4Tests :: TestTree+ipv4Tests = testGroup "IPv4"+  [ QC.testProperty "round trips" $+    \ipv4 -> readIPv4 (toText (ipv4 :: IPv4)) == Just ipv4++  , testCase "parses and formats sample IPs" $+    mapM_ (\t -> toText (fromJust (readIPv4 t)) @?= t)+    [ "0.0.0.0"+    , "0.0.0.1"+    , "1.1.1.1"+    , "127.0.0.1"+    , "192.168.1.1"+    , "255.255.255.0"+    , "255.255.255.255"+    ]++  , testCase "fails to parse bad IPs" $ mapM_ (\t -> readIPv4 t @?= Nothing)+    [ ""+    , "foo"+    , "..."+    , "127 0 0 1"+    , "1 27.0.0.1"+    , "256.0.0.0"+    , "-1.0.0.0"+    , "1.2.3.4.5"+    , "1.2.3.ff"+    ]++  , testCase "Bounded instance" $ do+      (minBound :: IPv4) @?= IPv4 0+      (maxBound :: IPv4) @?= IPv4 0xFFFFFFFF+  ]++ipv6Tests :: TestTree+ipv6Tests = testGroup "IPv6"+  [ testCase "parses sample IPs" $ do+      readIPv6 "::" @?= Just (IPv6 0 0 0 0)+      readIPv6 "::1" @?= Just (IPv6 0 0 0 1)+      readIPv6 "::2" @?= Just (IPv6 0 0 0 2)+      readIPv6 "::ffff" @?= Just (IPv6 0 0 0 0xFFFF)+      readIPv6 "::ff:ffff" @?= Just (IPv6 0 0 0 0xFFFFFF)+      readIPv6 "::ffff:ffff" @?= Just (IPv6 0 0 0 0xFFFFFFFF)+      readIPv6 "2001:0DB8:AC10:FE01:0000:0000:0000:0000" @?=+        Just (IPv6 0x20010DB8 0xAC10FE01 0 0)+      readIPv6 "2001:0DB8:AC10:FE01:0:0:0:0" @?=+        Just (IPv6 0x20010DB8 0xAC10FE01 0 0)+      readIPv6 "2001:db8:ac10:fe01::" @?=+        Just (IPv6 0x20010DB8 0xAC10FE01 0 0)++  , testCase "round trips RFC 5952 IPs" $+    mapM_ (\t -> toText (fromJust (readIPv6 t)) @?= t)+    [ "::"+    , "::1"+    , "::1:1"+    , "1::"+    , "1::1"+    , "1::1:1"+    , "2001:db8:85a3:8d3:1319:8a2e:370:7348"+    ]++  , QC.testProperty "round trips arbitrary IPs" $+    \ip -> readIPv6 (toText (ip :: IPv6)) == Just ip++  , testCase "Bounded instance" $ do+      (minBound :: IPv6) @?= IPv6 0 0 0 0+      (maxBound :: IPv6) @?= IPv6 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF+  ]++multiaddrTests :: TestTree+multiaddrTests = testGroup "multiaddr"+  [ testCase "round trips sample multiaddrs via text" $+    mapM_ (\t -> toText (fromJust (readMultiaddr t)) @?= t)+    [ ""+    , "/ip4/127.0.0.1"+    , "/ip4/127.0.0.1/tcp/80"+    , "/ip4/127.0.0.1/tcp/80/ip6/::1"+    , "/ip4/127.0.0.1/tcp/80/ip6/::1/udp/1234"+    , "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"+    , "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234"+    , "/ip4/8.4.203.49/udp/40/udt"+    , "/ip4/8.4.203.49/udp/40/utp"+    ]++  , testCase "round trips sample multiaddrs via binary" $+    mapM_ (\t -> let ma = fromJust (readMultiaddr t)+                 in fromJust (decode (encode ma)) @?= ma)+    [ ""+    , "/ip4/127.0.0.1"+    , "/ip4/127.0.0.1/tcp/80"+    , "/ip4/127.0.0.1/tcp/80/ip6/::1"+    , "/ip4/127.0.0.1/tcp/80/ip6/::1/udp/1234"+    , "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"+    , "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234"+    , "/ip4/8.4.203.49/udp/40/udt"+    , "/ip4/8.4.203.49/udp/40/utp"+    ]++  , testCase "protocolNames" $+    mapM_ (\(ma, ps) -> protocolNames (fromJust (readMultiaddr ma)) @?= ps)+    [ ("", [])+    , ("/ip4/127.0.0.1", ["ip4"])+    , ("/ip4/127.0.0.1/tcp/80", ["ip4", "tcp"])+    , ("/ip4/127.0.0.1/tcp/80/ip6/::1", ["ip4", "tcp", "ip6"])+    , ("/ip4/127.0.0.1/tcp/80/ip6/::1/udp/1234", ["ip4", "tcp", "ip6", "udp"])+    , ("/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", ["ipfs"])+    , ("/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",+       ["ipfs", "tcp"])+    , ("/ip4/8.4.203.49/udp/40/udt", ["ip4", "udp", "udt"])+    , ("/ip4/8.4.203.49/udp/40/utp", ["ip4", "udp", "utp"])+    ]+  ]++tests :: TestTree+tests = testGroup "Network.Multiaddr" [ipv4Tests, ipv6Tests, multiaddrTests]++main :: IO ()+main = defaultMain tests