network-ip 0.1 → 0.2
raw patch · 3 files changed
+366/−12 lines, 3 filesdep +data-default-classdep +tastydep +tasty-quickcheckdep −QuickCheckdep −data-defaultdep −test-framework
Dependencies added: data-default-class, tasty, tasty-quickcheck
Dependencies removed: QuickCheck, data-default, test-framework, test-framework-quickcheck2
Files
- network-ip.cabal +5/−6
- src/Network/IP/Addr.hs +145/−2
- tests/Tests.hs +216/−4
network-ip.cabal view
@@ -1,5 +1,5 @@ Name: network-ip-Version: 0.1+Version: 0.2 Category: Network Stability: experimental Synopsis: Internet Protocol data structures@@ -26,7 +26,7 @@ Default-Language: Haskell2010 Build-Depends: base >= 4.3 && < 5,- data-default >= 0.2,+ data-default-class, data-endian >= 0.0.1, data-dword >= 0.2, tagged >= 0.2,@@ -45,10 +45,9 @@ Default-Language: Haskell2010 Type: exitcode-stdio-1.0 Build-Depends:- base >= 4 && < 5,- test-framework >= 0.6,- test-framework-quickcheck2 >= 0.2,- QuickCheck >= 2.4,+ base >= 4 && < 5,+ tasty >= 0.4,+ tasty-quickcheck >= 0.3, data-dword, text-printer, data-textual,
src/Network/IP/Addr.hs view
@@ -24,6 +24,10 @@ , ip4FromOctets , ip4FromOctetList , anyIP4+ , loopbackIP4+ , broadcastIP4+ , Range4(..)+ , ip4Range -- ** IPv6 address , IP6(..) , anIP6@@ -32,6 +36,9 @@ , ip6FromWords , ip6FromWordList , anyIP6+ , loopbackIP6+ , Range6(..)+ , ip6Range -- ** IP address , IP(..) , anIP@@ -45,6 +52,8 @@ , aNet4Addr , aNet6Addr , aNetAddrIP+ , net4Addr+ , net6Addr , printNetAddr , net4Parser , net6Parser@@ -71,7 +80,7 @@ import Data.DoubleWord (BinaryWord(..), DoubleWord(..), Word128) import Data.Ix (Ix) import Data.Endian-import Data.Default+import Data.Default.Class import Data.Hashable import Data.Binary (Binary) import qualified Data.Binary as B@@ -183,6 +192,63 @@ def = anyIP4 {-# INLINE def #-} +-- | IPv4 address @127.0.0.1@.+loopbackIP4 ∷ IP4+loopbackIP4 = IP4 0x7F000001+{-# INLINE loopbackIP4 #-}++-- | IPv4 address @255.255.255.255@.+broadcastIP4 ∷ IP4+broadcastIP4 = IP4 0xFFFFFFFF+{-# INLINE broadcastIP4 #-}++-- | IPv4 address range classification (per RFC6890).+data Range4 = GeneralIP4 -- ^ General IPv4 address.+ | ThisHostIP4 -- ^ This host on this network.+ | PrivateUseIP4 -- ^ Private-Use networks.+ | SharedSpaceIP4 -- ^ Shared address space.+ | LoopbackIP4 -- ^ Loopback address.+ | LinkLocalIP4 -- ^ Link local address.+ | ReservedIP4 -- ^ Reserved address.+ | DSLiteIP4 -- ^ Dual-Stack Lite.+ | DocumentationIP4 -- ^ Reserved for documentation.+ | IP6To4IP4 -- ^ 6to4.+ | BenchmarkingIP4 -- ^ Benchmark testing.+ | MulticastIP4 -- ^ Multicast address.+ | FutureUseIP4 -- ^ Future use.+ | BroadcastIP4 -- ^ Limited broadcast.+ deriving (Typeable, Show, Read, Eq, Ord, Enum)++-- | Determine the address range type.+ip4Range ∷ IP4 → Range4+ip4Range addr = case w1 of+ 0 → ThisHostIP4+ 10 → PrivateUseIP4+ 100 → if w2 .&. 0xC0 == 0x40 then SharedSpaceIP4 else GeneralIP4+ 127 → LoopbackIP4+ 169 → if w2 == 254 then LinkLocalIP4 else GeneralIP4+ 172 → if w2 .&. 0xF0 == 0x10 then PrivateUseIP4 else GeneralIP4+ 192 → case w2 of+ 0 → case w3 of+ 0 → if w4 <= 7 then DSLiteIP4 else ReservedIP4+ 2 → DocumentationIP4+ _ → GeneralIP4+ 88 → if w3 == 99 then IP6To4IP4 else GeneralIP4+ 168 → PrivateUseIP4+ _ → GeneralIP4+ 198 → case w2 of+ 18 → BenchmarkingIP4+ 19 → BenchmarkingIP4+ 51 → if w3 == 100 then DocumentationIP4 else GeneralIP4+ _ → GeneralIP4+ 203 → if w2 == 0 && w3 == 113 then DocumentationIP4 else GeneralIP4+ _ | addr == broadcastIP4 → BroadcastIP4+ _ → case w1 .&. 0xF0 of+ 224 → MulticastIP4+ 240 → FutureUseIP4+ _ → GeneralIP4+ where (w1, w2, w3, w4) = ip4ToOctets addr+ -- | IPv6 address. newtype IP6 = IP6 { unIP6 ∷ Word128 } deriving (Typeable, Eq, Ord, Bounded, Enum, Ix, Num, Bits, Hashable)@@ -360,6 +426,60 @@ def = anyIP6 {-# INLINE def #-} +-- | IPv6 address @::1@.+loopbackIP6 ∷ IP6+loopbackIP6 = IP6 1++-- | IPv6 address range classification (per RFC6890).+data Range6 = GeneralIP6 -- ^ General IPv6 address.+ | AnyIP6 -- ^ Unspecified address.+ | LoopbackIP6 -- ^ Loopback address.+ | IP4MappedIP6 -- ^ Mapped IPv4 address.+ | IP4EmbeddedIP6 -- ^ Embedded IPv4 address.+ | DiscardIP6 -- ^ Discard address.+ | ReservedIP6 -- ^ Reserved address.+ | TeredoIP6 -- ^ Teredo address.+ | BenchmarkingIP6 -- ^ Benchmark testing.+ | DocumentationIP6 -- ^ Reserved for documentation.+ | OrchidIP6 -- ^ ORCHID address.+ | IP6To4IP6 -- ^ 6to4.+ | UniqueLocalIP6 -- ^ Unique local address.+ | LinkLocalIP6 -- ^ Link local address.+ | MulticastIP6 -- ^ Multicast address.+ deriving (Typeable, Show, Read, Eq, Ord, Enum)++-- | Determine the address range type.+ip6Range ∷ IP6 → Range6+ip6Range (IP6 w) = case hi of+ 0 → case lo of+ 0 → AnyIP6+ 1 → LoopbackIP6+ _ → GeneralIP6+ 0x000000000000FFFF → if q3 == 0 then IP4MappedIP6 else GeneralIP6+ 0x0064FF9B00000000 → if q3 == 0 then IP4EmbeddedIP6 else GeneralIP6+ 0x0100000000000000 → DiscardIP6+ _ → case w1 of+ 0x2001 → case w2 of+ 0 → TeredoIP6+ 2 → if w3 == 0 then BenchmarkingIP6 else GeneralIP6+ 0xDB8 → DocumentationIP6+ _ | w2 .&. 0xFFF0 == 0x10 → OrchidIP6+ | w2 .&. 0xFE00 == 0 → ReservedIP6 + | otherwise → GeneralIP6+ 0x2002 → IP6To4IP6+ _ | w1 .&. 0xFE00 == 0xFC00 → UniqueLocalIP6+ | w1 .&. 0xFFC0 == 0xFE80 → LinkLocalIP6+ | w1 >= 0xFF00 → MulticastIP6+ | otherwise → GeneralIP6+ where hi = hiWord w+ lo = loWord w+ q1 = hiWord hi+ q2 = loWord hi+ q3 = hiWord lo+ w1 = hiWord q1+ w2 = loWord q1+ w3 = hiWord q2+ -- IP address. data IP = IPv4 {-# UNPACK #-} !IP4 | IPv6 {-# UNPACK #-} !IP6@@ -406,6 +526,10 @@ netAddr ∷ NetHost n -- ^ Host address → Word8 -- ^ Network prefix length → n+ -- | Test if the address is in the network.+ inNetwork ∷ NetHost n -- ^ Host address+ → n -- ^ Network address+ → Bool -- | Network address: host address + network mask length. data NetAddr a = NetAddr a {-# UNPACK #-} !Word8@@ -519,6 +643,9 @@ {-# INLINE netLength #-} netAddr a w = NetAddr a (w `min` 32) {-# INLINE netAddr #-}+ inNetwork h (NetAddr a w) = h `shiftR` l == a `shiftR` l+ where l = 32 - fromIntegral w+ {-# INLINE inNetwork #-} instance IsNetAddr Net6Addr where type NetHost Net6Addr = IP6@@ -537,6 +664,9 @@ {-# INLINE netLength #-} netAddr a w = NetAddr a (w `min` 128) {-# INLINE netAddr #-}+ inNetwork h (NetAddr a w) = h `shiftR` l == a `shiftR` l+ where l = 128 - fromIntegral w+ {-# INLINE inNetwork #-} instance IsNetAddr (NetAddr IP) where type NetHost (NetAddr IP) = IP@@ -566,7 +696,20 @@ IPv4 _ → 32 IPv6 _ → 128 {-# INLINABLE netAddr #-}+ inNetwork (IPv4 h) (NetAddr (IPv4 a) w) = h `shiftR` l == a `shiftR` l+ where l = 32 - fromIntegral w+ inNetwork (IPv6 h) (NetAddr (IPv6 a) w) = h `shiftR` l == a `shiftR` l+ where l = 128 - fromIntegral w+ inNetwork _ _ = False +net4Addr ∷ IP4 → Word8 → Net4Addr+net4Addr = netAddr+{-# INLINE net4Addr #-}++net6Addr ∷ IP6 → Word8 → Net6Addr+net6Addr = netAddr+{-# INLINE net6Addr #-}+ -- | Print network address (CIDR notation). printNetAddr ∷ (IsNetAddr n, Printable (NetHost n), Printer p) ⇒ n → p printNetAddr n = print (netHost n) <> P.char7 '/' <> print (netLength n)@@ -644,7 +787,7 @@ type Inet6Addr = InetAddr IP6 -- | 'InetAddr' proxy value.-anInetAddr ∷ Proxy (InetAddr IP)+anInetAddr ∷ Proxy InetAddr anInetAddr = Proxy -- | 'InetAddr' /a/ proxy value.
tests/Tests.hs view
@@ -1,12 +1,12 @@ {-# LANGUAGE UnicodeSyntax #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-} -import Test.Framework (defaultMain, testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck+import Test.Tasty (defaultMain, testGroup)+import Test.Tasty.QuickCheck import Data.DoubleWord import Data.Textual@@ -52,7 +52,7 @@ arbitrary = InetAddr <$> arbitrary <*> arbitrary shrink n = uncurry InetAddr <$> shrink (inetHost n, inetPort n) -main = defaultMain+main = defaultMain $ testGroup "Tests" [ testGroup "IP4" [ testProperty "show -> read" $ \a → read (show a) == (a ∷ IP4)@@ -80,6 +80,101 @@ fromStringAs anIP4 "1.2.3.4.5" == Nothing , testProperty "parsing \"1a.2.3.4\" fails" $ fromStringAs anIP4 "1a.2.3.4" == Nothing+ , testProperty "0.1.2.3 is in the 'This host' range" $+ ip4Range (ip4FromOctets 0 1 2 3) == ThisHostIP4+ , testProperty "10.1.2.3 is in the 'Private-Use' range" $+ ip4Range (ip4FromOctets 10 1 2 3) == PrivateUseIP4+ , testProperty "100.64.1.2 is in the 'Shared address space' range" $+ ip4Range (ip4FromOctets 100 64 1 2) == SharedSpaceIP4+ , testProperty "100.97.1.2 is in the 'Shared address space' range" $+ ip4Range (ip4FromOctets 100 97 1 2) == SharedSpaceIP4+ , testProperty "100.128.1.2 is in the 'General' range" $+ ip4Range (ip4FromOctets 100 128 1 2) == GeneralIP4+ , testProperty "127.0.0.1 is in the 'Loopback' range" $+ ip4Range loopbackIP4 == LoopbackIP4+ , testProperty "169.254.0.1 is in the 'Link Local' range" $+ ip4Range (ip4FromOctets 169 254 0 1) == LinkLocalIP4+ , testProperty "172.16.1.2 is in the 'Private-Use' range" $+ ip4Range (ip4FromOctets 172 16 1 2) == PrivateUseIP4+ , testProperty "172.31.1.2 is in the 'Private-Use' range" $+ ip4Range (ip4FromOctets 172 31 1 2) == PrivateUseIP4+ , testProperty "172.48.1.2 is in the 'General' range" $+ ip4Range (ip4FromOctets 172 48 1 2) == GeneralIP4+ , testProperty "192.0.0.7 is in the 'DS-Lite' range" $+ ip4Range (ip4FromOctets 192 0 0 7) == DSLiteIP4+ , testProperty "192.0.0.8 is in the 'Reserved' range" $+ ip4Range (ip4FromOctets 192 0 0 8) == ReservedIP4+ , testProperty "192.0.2.1 is in the 'Documentation' range" $+ ip4Range (ip4FromOctets 192 0 2 1) == DocumentationIP4+ , testProperty "192.88.99.1 is in the '6to4' range" $+ ip4Range (ip4FromOctets 192 88 99 1) == IP6To4IP4+ , testProperty "192.168.1.2 is in the 'Private-Use' range" $+ ip4Range (ip4FromOctets 192 168 1 2) == PrivateUseIP4+ , testProperty "198.18.1.2 is in the 'Benchmarking' range" $+ ip4Range (ip4FromOctets 198 18 1 2) == BenchmarkingIP4+ , testProperty "198.19.1.2 is in the 'Benchmarking' range" $+ ip4Range (ip4FromOctets 198 19 1 2) == BenchmarkingIP4+ , testProperty "198.20.1.2 is in the 'General' range" $+ ip4Range (ip4FromOctets 198 20 1 2) == GeneralIP4+ , testProperty "198.51.100.1 is in the 'Documentation' range" $+ ip4Range (ip4FromOctets 198 51 100 1) == DocumentationIP4+ , testProperty "198.51.101.1 is in the 'General' range" $+ ip4Range (ip4FromOctets 198 51 101 1) == GeneralIP4+ , testProperty "198.52.100.1 is in the 'General' range" $+ ip4Range (ip4FromOctets 198 52 100 1) == GeneralIP4+ , testProperty "203.0.113.1 is in the 'Documentation' range" $+ ip4Range (ip4FromOctets 203 0 113 1) == DocumentationIP4+ , testProperty "203.0.114.1 is in the 'General' range" $+ ip4Range (ip4FromOctets 203 0 114 1) == GeneralIP4+ , testProperty "203.1.113.1 is in the 'General' range" $+ ip4Range (ip4FromOctets 203 1 113 1) == GeneralIP4+ , testProperty "224.1.2.3 is in the 'Multicast' range" $+ ip4Range (ip4FromOctets 224 1 2 3) == MulticastIP4+ , testProperty "239.1.2.3 is in the 'Multicast' range" $+ ip4Range (ip4FromOctets 239 1 2 3) == MulticastIP4+ , testProperty "223.1.2.3 is in the 'General' range" $+ ip4Range (ip4FromOctets 223 1 2 3) == GeneralIP4+ , testProperty "240.1.2.3 is in the 'Future use' range" $+ ip4Range (ip4FromOctets 240 1 2 3) == FutureUseIP4+ , testProperty "255.255.255.254 is in the 'Future use' range" $+ ip4Range (ip4FromOctets 255 255 255 254) == FutureUseIP4+ , testProperty "255.255.255.255 is in the 'Broadcast' range" $+ ip4Range broadcastIP4 == BroadcastIP4+ , testProperty "ip4Range" $ \a →+ let r = ip4Range a in+ if | inNetwork a (net4Addr anyIP4 8)+ → r == ThisHostIP4+ | inNetwork a (net4Addr (ip4FromOctets 10 0 0 0) 8) ||+ inNetwork a (net4Addr (ip4FromOctets 172 16 0 0) 12) ||+ inNetwork a (net4Addr (ip4FromOctets 192 168 0 0) 16)+ → r == PrivateUseIP4+ | inNetwork a (net4Addr (ip4FromOctets 100 64 0 0) 10)+ → r == SharedSpaceIP4+ | inNetwork a (net4Addr (ip4FromOctets 127 0 0 0) 8)+ → r == LoopbackIP4+ | inNetwork a (net4Addr (ip4FromOctets 169 254 0 0) 16)+ → r == LinkLocalIP4+ | inNetwork a (net4Addr (ip4FromOctets 192 0 0 0) 24)+ → if inNetwork a (net4Addr (ip4FromOctets 192 0 0 0) 29)+ then r == DSLiteIP4+ else r == ReservedIP4+ | inNetwork a (net4Addr (ip4FromOctets 192 0 2 0) 24) ||+ inNetwork a+ (net4Addr (ip4FromOctets 198 51 100 0) 24) ||+ inNetwork a (net4Addr (ip4FromOctets 203 0 113 0) 24)+ → r == DocumentationIP4+ | inNetwork a (net4Addr (ip4FromOctets 192 88 99 0) 24)+ → r == IP6To4IP4+ | inNetwork a (net4Addr (ip4FromOctets 198 18 0 0) 15)+ → r == BenchmarkingIP4+ | inNetwork a (net4Addr (ip4FromOctets 224 0 0 0) 4)+ → r == MulticastIP4+ | inNetwork a (net4Addr (ip4FromOctets 240 0 0 0) 4)+ → if a == broadcastIP4+ then r == BroadcastIP4+ else r == FutureUseIP4+ | otherwise+ → r == GeneralIP4 ] , testGroup "IP6" [ testProperty "show -> read" $ \a →@@ -133,6 +228,123 @@ fromStringAs anIP6 "1:2::3:4:5g" == Nothing , testProperty "parsing \"1:2:3:4:5:6:7:8g\" fails" $ fromStringAs anIP6 "1:2:3:4:5:6:7:8g" == Nothing+ , testProperty ":: is in the 'Unspecified' range" $+ ip6Range (IP6 0) == AnyIP6+ , testProperty "::1 is in the 'Loopback' range" $+ ip6Range (IP6 1) == LoopbackIP6+ , testProperty "::ffff:0:0:102:304 is in the 'IPv4 mapped' range" $+ ip6Range (ip6FromWords 0 0 0 0xFFFF 0 0 0x102 0x304) ==+ IP4MappedIP6+ , testProperty "64:ff9b::102:304 is in the 'IPv4 embedded' range" $+ ip6Range (ip6FromWords 0x64 0xFF9B 0 0 0 0 0x102 0x304) ==+ IP4EmbeddedIP6+ , testProperty "100::1:2:3:4 is in the 'Discard' range" $+ ip6Range (ip6FromWords 0x100 0 0 0 1 2 3 4) == DiscardIP6+ , testProperty "100::1:2:3:4:5 is in the 'General' range" $+ ip6Range (ip6FromWords 0x100 0 0 1 2 3 4 5) == GeneralIP6+ , testProperty "2001::ffff:1:2:3:4:5 is in the 'Teredo' range" $+ ip6Range (ip6FromWords 0x2001 0 0xFFFF 1 2 3 4 5) ==+ TeredoIP6+ , testProperty "2001:2::0xFFFF:1:2:3:4 is in the 'Benchmarking' range" $+ ip6Range (ip6FromWords 0x2001 2 0 0xFFFF 1 2 3 4) ==+ BenchmarkingIP6+ , testProperty "2001:2:1:2:3:4:5:6 is in the 'General' range" $+ ip6Range (ip6FromWords 0x2001 2 1 2 3 4 5 6) == GeneralIP6+ , testProperty "2001:db8:0xFFFF:1:2:3:4:5 is in the 'Documentation' range" $+ ip6Range (ip6FromWords 0x2001 0xDB8 0xFFFF 1 2 3 4 5) ==+ DocumentationIP6+ , testProperty "2001:db9:1:2:3:4:5:6 is in the 'General' range" $+ ip6Range (ip6FromWords 0x2001 0xDB9 1 2 3 4 5 6) ==+ GeneralIP6+ , testProperty "2001:10:0xFFFF:1:2:3:4:5 is in the 'Orchid' range" $+ ip6Range (ip6FromWords 0x2001 0x10 0xFFFF 1 2 3 4 5) ==+ OrchidIP6+ , testProperty "2001:18:1:2:3:4:5:6 is in the 'Orchid' range" $+ ip6Range (ip6FromWords 0x2001 0x18 1 2 3 4 5 6) ==+ OrchidIP6+ , testProperty "2001:100:1:2:3:4:5:6 is in the 'Reserved' range" $+ ip6Range (ip6FromWords 0x2001 0x100 1 2 3 4 5 6) ==+ ReservedIP6+ , testProperty "2001:200:1:2:3:4:5:6 is in the 'General' range" $+ ip6Range (ip6FromWords 0x2001 0x200 1 2 3 4 5 6) ==+ GeneralIP6+ , testProperty "2002::1:2:3:4:5:6 is in the '6to4' range" $+ ip6Range (ip6FromWords 0x2002 0 1 2 3 4 5 6) ==+ IP6To4IP6+ , testProperty "2002:ffff:1:2:3:4:5:6 is in the '6to4' range" $+ ip6Range (ip6FromWords 0x2002 0xFFFF 1 2 3 4 5 6) ==+ IP6To4IP6+ , testProperty "fc00::1:2:3:4:5:6 is in the 'Unique Local' range" $+ ip6Range (ip6FromWords 0xFC00 0 1 2 3 4 5 6) ==+ UniqueLocalIP6+ , testProperty "fdff::1:2:3:4:5:6 is in the 'Unique Local' range" $+ ip6Range (ip6FromWords 0xFDFF 0 1 2 3 4 5 6) ==+ UniqueLocalIP6+ , testProperty "fe00::1:2:3:4:5:6 is in the 'General' range" $+ ip6Range (ip6FromWords 0xFE00 0 1 2 3 4 5 6) ==+ GeneralIP6+ , testProperty "fe80::1:2:3:4:5:6 is in the 'Link Local' range" $+ ip6Range (ip6FromWords 0xFE80 0 1 2 3 4 5 6) ==+ LinkLocalIP6+ , testProperty "febf::1:2:3:4:5:6 is in the 'Link Local' range" $+ ip6Range (ip6FromWords 0xFEBF 0 1 2 3 4 5 6) ==+ LinkLocalIP6+ , testProperty "fec0::1:2:3:4:5:6 is in the 'General' range" $+ ip6Range (ip6FromWords 0xFEC0 0 1 2 3 4 5 6) ==+ GeneralIP6+ , testProperty "fe00::1:2:3:4:5:6 is in the 'General' range" $+ ip6Range (ip6FromWords 0xFE00 0 1 2 3 4 5 6) ==+ GeneralIP6+ , testProperty "ff00::1:2:3:4:5:6 is in the 'Multicast' range" $+ ip6Range (ip6FromWords 0xFF00 0 1 2 3 4 5 6) ==+ MulticastIP6+ , testProperty "ffff::1:2:3:4:5:6 is in the 'Multicast' range" $+ ip6Range (ip6FromWords 0xFFFF 0 1 2 3 4 5 6) ==+ MulticastIP6+ , testProperty "ip6Range" $ \a →+ let r = ip6Range a in+ if | a == anyIP6+ → r == AnyIP6+ | a == loopbackIP6+ → r == LoopbackIP6 + | inNetwork a+ (net6Addr (ip6FromWords 0 0 0 0xFFFF 0 0 0 0) 96)+ → r == IP4MappedIP6+ | inNetwork a+ (net6Addr (ip6FromWords 0x100 0 0 0 0 0 0 0) 64)+ → r == DiscardIP6+ | inNetwork a+ (net6Addr (ip6FromWords 0x64 0xFF9B 0 0 0 0 0 0) 96)+ → r == IP4EmbeddedIP6+ | inNetwork a+ (net6Addr (ip6FromWords 0x2001 0 0 0 0 0 0 0) 23)+ → if inNetwork a+ (net6Addr (ip6FromWords 0x2001 0 0 0 0 0 0 0) 32)+ then r == TeredoIP6+ else+ if inNetwork a+ (net6Addr+ (ip6FromWords 0x2001 0x10 0 0 0 0 0 0) 28)+ then r == OrchidIP6+ else r == ReservedIP6+ | inNetwork a+ (net6Addr+ (ip6FromWords 0x2001 0xDB8 0 0 0 0 0 0) 32)+ → r == DocumentationIP6+ | inNetwork a+ (net6Addr (ip6FromWords 0x2002 0 0 0 0 0 0 0) 16)+ → r == IP6To4IP6+ | inNetwork a+ (net6Addr (ip6FromWords 0xFC00 0 0 0 0 0 0 0) 7)+ → r == UniqueLocalIP6+ | inNetwork a+ (net6Addr (ip6FromWords 0xFE80 0 0 0 0 0 0 0) 10)+ → r == LinkLocalIP6+ | inNetwork a+ (net6Addr (ip6FromWords 0xFF00 0 0 0 0 0 0 0) 8)+ → r == MulticastIP6+ | otherwise+ → r == GeneralIP6 ] , testGroup "IP" [ testProperty "show -> read" $ \a →