packages feed

ip 1.7.0 → 1.7.1

raw patch · 6 files changed

+272/−44 lines, 6 filesdep +randomdep ~aesondep ~byteslicedep ~bytesmithPVP ok

version bump matches the API change (PVP)

Dependencies added: random

Dependency ranges changed: aeson, byteslice, bytesmith, primitive, small-bytearray-builder

API changes (from Hackage documentation)

+ Net.IPv4: parserRangeUtf8Bytes :: e -> Parser e s IPv4Range
+ Net.IPv4: parserRangeUtf8BytesLenient :: e -> Parser e s IPv4Range
+ Net.IPv6: parserRangeUtf8Bytes :: e -> Parser e s IPv6Range
+ Net.IPv6: parserRangeUtf8BytesLenient :: e -> Parser e s IPv6Range
+ Net.Mac: boundedBuilderUtf8 :: Mac -> Builder 17
+ Net.Mac: decodeUtf8Bytes :: Bytes -> Maybe Mac
+ Net.Mac: parserUtf8Bytes :: e -> Parser e s Mac

Files

ip.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: ip-version: 1.7.0+version: 1.7.1 synopsis: Library for IP and MAC addresses homepage: https://github.com/andrewthad/haskell-ip#readme license: BSD-3-Clause@@ -49,14 +49,14 @@     , aeson >= 1.0 && < 1.5     , attoparsec >= 0.13 && < 0.14     , base >= 4.9 && < 5-    , byteslice >= 0.1.2 && < 0.2-    , bytesmith >= 0.3 && < 0.4+    , byteslice >= 0.1.2 && < 0.3+    , bytesmith >= 0.3.3 && < 0.4     , bytestring >= 0.10.8 && < 0.11     , deepseq >= 1.4 && < 1.5     , hashable >= 1.2 && < 1.4     , natural-arithmetic >= 0.1 && <0.2     , primitive >= 0.6.4 && < 0.8-    , small-bytearray-builder >= 0.2.1 && <0.4+    , small-bytearray-builder >= 0.3.2 && <0.4     , text >= 1.2 && < 1.3     , text-short >= 0.1.3 && < 0.2     , vector >= 0.11 && < 0.13@@ -73,7 +73,7 @@     , QuickCheck     , attoparsec     , base-    , byteslice >= 0.1.2 && < 0.2+    , byteslice     , bytestring     , ip     , quickcheck-classes >= 0.4.13 && < 0.7.0.0@@ -128,7 +128,9 @@     , bytestring     , criterion     , ip+    , primitive     , text+    , random   other-modules:     IPv4ByteString1     IPv4DecodeText1
src/Net/IPv4.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-}@@ -76,6 +77,9 @@   , builderRange   , parserRange   , printRange+    -- ** UTF-8 Bytes+  , parserRangeUtf8Bytes+  , parserRangeUtf8BytesLenient     -- * Types   , IPv4(..)   , IPv4#@@ -235,51 +239,71 @@   || mask12 .&. w == p20   || mask16 .&. w == p16 +----------------------------------------+-- Note [The implementation of reserved]+----------------------------------------+-- The @reserved@ function has been optimized to perform well in the+-- microbenchmark @CIDR Inclusion/reserved@. We perform an inital case+-- on the upper three bits (8 possible values), which GHC will compile+-- to a jump table. This helps because the reserved ranges of IPv4+-- addresses are somewhat clustered. Notice that everything in+-- 32.0.0.0/3, 64.0.0.0/3, and 128.0.0.0/3 is publicly routable, and+-- everything in 224.0.0.0/3 is reserved. This means that for exactly+-- half of the IPv4 addresses that exist, this single jump is sufficient+-- for determining whether or not they are reserved. For the others,+-- there is a little more work to do, particularly in the 192.0.0.0/3+-- range. On the laptop that ran the microbenchmark, this function+-- decided the reservedness of 100 random IPv4 addresses in 200ns.+ -- | Checks to see if the 'IPv4' address belongs to a reserved -- network. This includes the three private networks that 'private' -- checks along with several other ranges that are not used--- on the public Internet.+-- on the public Internet. The implementation of this function+-- is optimized. reserved :: IPv4 -> Bool-reserved =-  let a = getIPv4 $ fromOctets' 0 0 0 0-      b = getIPv4 $ fromOctets' 100 64 0 0-      c = getIPv4 $ fromOctets' 127 0 0 0-      d = getIPv4 $ fromOctets' 169 254 0 0-      e = getIPv4 $ fromOctets' 192 0 0 0-      f = getIPv4 $ fromOctets' 192 0 2 0-      g = getIPv4 $ fromOctets' 192 88 99 0-      h = getIPv4 $ fromOctets' 198 18 0 0-      i = getIPv4 $ fromOctets' 198 51 100 0-      j = getIPv4 $ fromOctets' 203 0 113 0-      k = getIPv4 $ fromOctets' 224 0 0 0-      l = getIPv4 $ fromOctets' 240 0 0 0-      m = getIPv4 $ fromOctets' 255 255 255 255-  in \(IPv4 w) -> mask8  .&. w == p24-               || mask12 .&. w == p20-               || mask16 .&. w == p16-               || mask8  .&. w == a-               || mask10 .&. w == b-               || mask8  .&. w == c-               || mask16 .&. w == d-               || mask24 .&. w == e-               || mask24 .&. w == f-               || mask24 .&. w == g-               || mask15 .&. w == h-               || mask24 .&. w == i-               || mask24 .&. w == j-               || mask4  .&. w == k-               || mask4  .&. w == l-               || mask32 .&. w == m+reserved !(IPv4 w) = case unsafeShiftR w 29 of+  0 ->+    let a = getIPv4 $ fromOctets' 0 0 0 0+        y = getIPv4 $ fromOctets' 10 0 0 0+     in mask8  .&. w == a+     || mask8  .&. w == y+  1 -> False+  2 -> False+  3 ->+    let b = getIPv4 $ fromOctets' 100 64 0 0+        c = getIPv4 $ fromOctets' 127 0 0 0+     in mask8  .&. w == c+     || mask10 .&. w == b+  4 -> False+  5 ->+    let d = getIPv4 $ fromOctets' 169 254 0 0+        x = getIPv4 $ fromOctets' 172 16 0 0+     in mask12 .&. w == x+     || mask16 .&. w == d+  6 ->+    let e = getIPv4 $ fromOctets' 192 0 0 0+        f = getIPv4 $ fromOctets' 192 0 2 0+        g = getIPv4 $ fromOctets' 192 88 99 0+        h = getIPv4 $ fromOctets' 198 18 0 0+        i = getIPv4 $ fromOctets' 198 51 100 0+        j = getIPv4 $ fromOctets' 203 0 113 0+        z = getIPv4 $ fromOctets' 192 168 0 0+     in mask15 .&. w == h+     || mask16 .&. w == z+     || mask24 .&. w == e+     || mask24 .&. w == f+     || mask24 .&. w == g+     || mask24 .&. w == i+     || mask24 .&. w == j+  _ -> True -mask8,mask4,mask12,mask16,mask10,mask24,mask32,mask15 :: Word32-mask4  = 0xF0000000+mask8,mask12,mask16,mask10,mask24,mask15 :: Word32 mask8  = 0xFF000000 mask10 = 0xFFC00000 mask12 = 0xFFF00000 mask15 = 0xFFFE0000 mask16 = 0xFFFF0000 mask24 = 0xFFFFFF00-mask32 = 0xFFFFFFFF  -- | Checks to see if the 'IPv4' address is publicly routable. --@@ -447,6 +471,43 @@   Latin.char e '.'   !d <- Latin.decWord8 e   pure (getIPv4 (fromOctets a b c d))++-- | Parse UTF-8-encoded 'Bytes' into an 'IPv4Range'.+-- This requires the mask to be present.+--+-- >>> maybe (putStrLn "nope") printRange $ Parser.parseBytesMaybe (parserRangeUtf8Bytes ()) (Bytes.fromAsciiString "192.168.0.0/16")+-- 192.168.0.0/16+-- >>> maybe (putStrLn "nope") printRange $ Parser.parseBytesMaybe (parserRangeUtf8Bytes ()) (Bytes.fromAsciiString "10.10.10.1")+-- nope+--+-- See 'parserRangeUtf8BytesLenient' for a variant that treats+-- a missing mask as a @/32@ mask.+parserRangeUtf8Bytes :: e -> Parser.Parser e s IPv4Range+parserRangeUtf8Bytes e = do+  base <- parserUtf8Bytes e+  Latin.char e '/'+  theMask <- Latin.decWord8 e+  if theMask > 32+    then Parser.fail e+    else pure $! normalize (IPv4Range base theMask)++-- | Variant of 'parserRangeUtf8Bytes' that allows the mask+-- to be omitted. An omitted mask is treated as a @/32@ mask.+--+-- >>> maybe (putStrLn "nope") printRange $ Parser.parseBytesMaybe (parserRangeUtf8BytesLenient ()) (Bytes.fromAsciiString "192.168.0.0/16")+-- 192.168.0.0/16+-- >>> maybe (putStrLn "nope") printRange $ Parser.parseBytesMaybe (parserRangeUtf8BytesLenient ()) (Bytes.fromAsciiString "10.10.10.1")+-- 10.10.10.1/32+parserRangeUtf8BytesLenient :: e -> Parser.Parser e s IPv4Range+parserRangeUtf8BytesLenient e = do+  base <- parserUtf8Bytes e+  Latin.trySatisfy (=='/') >>= \case+    True -> do+      theMask <- Latin.decWord8 e+      if theMask > 32+        then Parser.fail e+        else pure $! normalize (IPv4Range base theMask)+    False -> pure $! IPv4Range base 32  -- | Encode an 'IPv4' as a bytestring 'Builder.Builder' --
src/Net/IPv6.hs view
@@ -56,6 +56,9 @@   , decodeRange   , parserRange   , printRange+    -- ** UTF-8 Bytes+  , parserRangeUtf8Bytes+  , parserRangeUtf8BytesLenient     -- * Types   , IPv6(..)   , IPv6Range(..)@@ -678,6 +681,7 @@     r <- pieceParserStep e w0     pure (Just r) +-- This should probably be moved into bytesmith and renamed. pieceParser :: e -> Parser.Parser e s Word16 pieceParser e = Latin.hexNibble e >>= pieceParserStep e @@ -696,6 +700,43 @@   else Latin.tryHexNibble >>= \case     Nothing -> pure (fromIntegral acc)     Just w -> pieceParserStep e (16 * acc + w)++-- | Parse UTF-8-encoded 'Bytes' into an 'IPv4Range'.+-- This requires the mask to be present.+--+-- >>> maybe (putStrLn "nope") printRange $ Parser.parseBytesMaybe (parserRangeUtf8Bytes ()) (Bytes.fromAsciiString "1b02:f001:5:200b::/80")+-- 1b02:f001:5:200b::/80+-- >>> maybe (putStrLn "nope") printRange $ Parser.parseBytesMaybe (parserRangeUtf8Bytes ()) (Bytes.fromAsciiString "abcd::")+-- nope+--+-- See 'parserRangeUtf8BytesLenient' for a variant that treats+-- a missing mask as a @/32@ mask.+parserRangeUtf8Bytes :: e -> Parser.Parser e s IPv6Range+parserRangeUtf8Bytes e = do+  base <- parserUtf8Bytes e+  Latin.char e '/'+  theMask <- Latin.decWord8 e+  if theMask > 128+    then Parser.fail e+    else pure $! normalize (IPv6Range base theMask)++-- | Variant of 'parserRangeUtf8Bytes' that allows the mask+-- to be omitted. An omitted mask is treated as a @/128@ mask.+--+-- >>> maybe (putStrLn "nope") printRange $ Parser.parseBytesMaybe (parserRangeUtf8BytesLenient ()) (Bytes.fromAsciiString "1b02:f001:5:200b::/80")+-- 1b02:f001:5:200b::/80+-- >>> maybe (putStrLn "nope") printRange $ Parser.parseBytesMaybe (parserRangeUtf8BytesLenient ()) (Bytes.fromAsciiString "abcd::")+-- abcd::/128+parserRangeUtf8BytesLenient :: e -> Parser.Parser e s IPv6Range+parserRangeUtf8BytesLenient e = do+  base <- parserUtf8Bytes e+  Latin.trySatisfy (=='/') >>= \case+    True -> do+      theMask <- Latin.decWord8 e+      if theMask > 128+        then Parser.fail e+        else pure $! normalize (IPv6Range base theMask)+    False -> pure $! IPv6Range base 128  -- | Parse an 'IPv6' using 'Atto.Parser'. --
src/Net/Mac.hs view
@@ -1,9 +1,11 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -37,6 +39,10 @@   , parserWithUtf8     -- ** ByteString   , decodeBytes+    -- ** UTF-8 Bytes+  , boundedBuilderUtf8+  , decodeUtf8Bytes+  , parserUtf8Bytes     -- ** Printing   , print     -- * Types@@ -71,11 +77,16 @@ import Text.ParserCombinators.ReadPrec (prec,step) import Text.Read (Read(..),Lexeme(Ident),lexP,parens) +import qualified Arithmetic.Nat as Nat import qualified Data.Aeson as Aeson import qualified Data.Aeson.Types as Aeson import qualified Data.Attoparsec.ByteString as AB import qualified Data.Attoparsec.ByteString as ABW import qualified Data.Attoparsec.Text as AT+import qualified Data.ByteArray.Builder.Bounded as BBB+import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Parser as Parser+import qualified Data.Bytes.Parser.Latin as Latin import qualified Data.ByteString as B import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Builder.Fixed as BFB@@ -121,19 +132,25 @@   , fromIntegral w   ) +-- | This function is deprecated. It will be renamed in a future release+--   since the name is misleading.+decodeBytes :: ByteString -> Maybe Mac+{-# DEPRECATED decodeBytes "Prefer decodeOctets" #-}+decodeBytes = decodeOctets+ -- | Decode a 'Mac' address from a 'ByteString'. Each byte is interpreted --   as an octet of the 'Mac' address. Consequently, 'ByteString's --   of length 6 successfully decode, and all other 'ByteString's fail --   to decode. -----   >>> decodeBytes (B.pack [0x6B,0x47,0x18,0x90,0x55,0xC3])+--   >>> decodeOctets (B.pack [0x6B,0x47,0x18,0x90,0x55,0xC3]) --   Just (mac 0x6b47189055c3)---   >>> decodeBytes (B.replicate 6 0x3A)+--   >>> decodeOctets (B.replicate 6 0x3A) --   Just (mac 0x3a3a3a3a3a3a)---   >>> decodeBytes (B.replicate 7 0x3A)+--   >>> decodeOctets (B.replicate 7 0x3A) --   Nothing-decodeBytes :: ByteString -> Maybe Mac-decodeBytes bs = if B.length bs == 6+decodeOctets :: ByteString -> Maybe Mac+decodeOctets bs = if B.length bs == 6   then Just $ fromOctets     (BU.unsafeIndex bs 0)     (BU.unsafeIndex bs 1)@@ -398,6 +415,74 @@  decodeLenientUtf8 :: ByteString -> Maybe Mac decodeLenientUtf8 bs = rightToMaybe (AB.parseOnly (parserLenientUtf8 <* AB.endOfInput) bs)++-- | Encode a 'Mac' address as colon-separated hexadecimal octets,+--   preferring lowercase for alphabetical characters.+--+--   >>> BBB.run Nat.constant $ boundedBuilderUtf8 $ mac 0xDEADBEEF1609+--   [0x64, 0x65, 0x3a, 0x61, 0x64, 0x3a, 0x62, 0x65, 0x3a, 0x65, 0x66, 0x3a, 0x31, 0x36, 0x3a, 0x30, 0x39]+boundedBuilderUtf8 :: Mac -> BBB.Builder 17+boundedBuilderUtf8 !w =+  BBB.word8PaddedLowerHex w0+  `BBB.append`+  BBB.ascii ':'+  `BBB.append`+  BBB.word8PaddedLowerHex w1+  `BBB.append`+  BBB.ascii ':'+  `BBB.append`+  BBB.word8PaddedLowerHex w2+  `BBB.append`+  BBB.ascii ':'+  `BBB.append`+  BBB.word8PaddedLowerHex w3+  `BBB.append`+  BBB.ascii ':'+  `BBB.append`+  BBB.word8PaddedLowerHex w4+  `BBB.append`+  BBB.ascii ':'+  `BBB.append`+  BBB.word8PaddedLowerHex w5+  where+  (w0,w1,w2,w3,w4,w5) = toOctets w++-- | Lenient decoding of MAC address. This+--   is case insensitive and allows either @:@ or @-@ as the separator.+--   It also allows leading zeroes to be missing.+--+--   >>> decodeUtf8Bytes (Bytes.fromAsciiString "A2:DE:AD:BE:EF:67")+--   Just (mac 0xa2deadbeef67)+--   >>> decodeUtf8Bytes (Bytes.fromAsciiString "13-a2-FE-A4-17-96")+--   Just (mac 0x13a2fea41796)+decodeUtf8Bytes :: Bytes.Bytes -> Maybe Mac+decodeUtf8Bytes = Parser.parseBytesMaybe (parserUtf8Bytes ())++-- | Leniently parse UTF-8-encoded 'Bytes' as a 'Mac' address. This+--   is case insensitive and allows either @:@ or @-@ as the separator.+--   It also allows leading zeroes to be missing.+--+--   >>> Parser.parseBytes (parserUtf8Bytes ()) (Bytes.fromAsciiString "de:ad:BE:EF:1:23")+--   Success (Slice {offset = 16, length = 0, value = mac 0xdeadbeef0123})+parserUtf8Bytes :: e -> Parser.Parser e s Mac+parserUtf8Bytes e = do+  w1 <- Latin.hexWord8 e+  Latin.any e >>= \case+    ':' -> do+      w2 <- Latin.hexWord8 e <* Latin.char e ':'+      w3 <- Latin.hexWord8 e <* Latin.char e ':'+      w4 <- Latin.hexWord8 e <* Latin.char e ':'+      w5 <- Latin.hexWord8 e <* Latin.char e ':'+      w6 <- Latin.hexWord8 e+      pure (fromOctets w1 w2 w3 w4 w5 w6)+    '-' -> do+      w2 <- Latin.hexWord8 e <* Latin.char e '-'+      w3 <- Latin.hexWord8 e <* Latin.char e '-'+      w4 <- Latin.hexWord8 e <* Latin.char e '-'+      w5 <- Latin.hexWord8 e <* Latin.char e '-'+      w6 <- Latin.hexWord8 e+      pure (fromOctets w1 w2 w3 w4 w5 w6)+    _ -> Parser.fail e  -- | Make a bytestring builder from a 'Mac' address --   using a colon as the separator.
test/Bench.hs view
@@ -3,6 +3,9 @@ import Criterion.Main import Net.Types (IPv4(..),MacGrouping(..),MacCodec(..)) import Data.Maybe (fromJust)+import Data.Primitive (PrimArray,foldlPrimArray')+import Data.Bool (bool)+import System.Random (mkStdGen,randoms) import qualified Data.Bytes as Bytes import qualified Data.Text as Text import qualified Net.Mac as Mac@@ -15,6 +18,7 @@ import qualified IPv4ByteString1 import qualified IPv4DecodeText1 import qualified IPv4DecodeText2+import qualified GHC.Exts as Exts -- import qualified IPv4TextVariableBuilder  main :: IO ()@@ -36,6 +40,7 @@       ip6Skip = fromJust $ IPv6.decode ip6TextSkip       ip6TextHex = Text.pack "a:b::c:d"       ip6Hex = fromJust $ IPv6.decode ip6TextHex+      hundredAddrs = Exts.fromList (map IPv4 (take 100 (randoms (mkStdGen 42)))) :: PrimArray IPv4   defaultMain     [ bgroup "Mac to Text"       [ bench "Current Implementation, pairs" $ whnf Mac.encode mac@@ -95,4 +100,16 @@       , bench "a:b::c:d" $ whnf IPv6.encodeShort ip6Hex       , bench "2001:db8:ba1:0:aaaa:542c:bb:cc00" $ whnf IPv6.encodeShort ip6Complicated       ]+    , bgroup "CIDR Inclusion"+      [ bench "reserved" $ whnf manyReserved hundredAddrs+      , bench "private" $ whnf manyPrivate hundredAddrs+      ]     ]++manyReserved :: PrimArray IPv4 -> Int+{-# noinline manyReserved #-}+manyReserved x = foldlPrimArray' (\acc addr -> bool 0 1 (IPv4.reserved addr) + acc) 0 x++manyPrivate :: PrimArray IPv4 -> Int+{-# noinline manyPrivate #-}+manyPrivate x = foldlPrimArray' (\acc addr -> bool 0 1 (IPv4.private addr) + acc) 0 x
test/Test.hs view
@@ -118,6 +118,28 @@     , testProperty "Normalize does not affect membership" propNormalizeMember     , testProperty "Membership agrees with bounds" propMemberUpperLower     , testProperty "Range contains self" propRangeSelf+    , testGroup "reserved"+      [ PH.testCase "A" $ IPv4.reserved (IPv4.ipv4 0 1 2 3) @=? True+      , PH.testCase "B" $ IPv4.reserved (IPv4.ipv4 1 0 0 0) @=? False+      , PH.testCase "C" $ IPv4.reserved (IPv4.ipv4 100 64 0 3) @=? True+      , PH.testCase "D" $ IPv4.reserved (IPv4.ipv4 127 255 255 255) @=? True+      , PH.testCase "E" $ IPv4.reserved (IPv4.ipv4 110 0 0 255) @=? False+      , PH.testCase "F" $ IPv4.reserved (IPv4.ipv4 192 0 2 255) @=? True+      , PH.testCase "G" $ IPv4.reserved (IPv4.ipv4 203 0 113 0) @=? True+      , PH.testCase "H" $ IPv4.reserved (IPv4.ipv4 225 0 0 0) @=? True+      , PH.testCase "I" $ IPv4.reserved (IPv4.ipv4 226 0 0 0) @=? True+      , PH.testCase "J" $ IPv4.reserved (IPv4.ipv4 255 255 255 254) @=? True+      , PH.testCase "K" $ IPv4.reserved (IPv4.ipv4 255 255 255 255) @=? True+      , PH.testCase "L" $ IPv4.reserved (IPv4.ipv4 224 0 0 0) @=? True+      , PH.testCase "M" $ IPv4.reserved (IPv4.ipv4 239 255 255 255) @=? True+      , PH.testCase "N" $ IPv4.reserved (IPv4.ipv4 223 255 255 255) @=? False+      , PH.testCase "O" $ IPv4.reserved (IPv4.ipv4 203 0 114 0) @=? False+      , PH.testCase "P" $ IPv4.reserved (IPv4.ipv4 203 0 112 255) @=? False+      , PH.testCase "Q" $ IPv4.reserved (IPv4.ipv4 203 0 113 255) @=? True+      , PH.testCase "R" $ IPv4.reserved (IPv4.ipv4 192 88 100 0) @=? False+      , PH.testCase "S" $ IPv4.reserved (IPv4.ipv4 192 88 99 0) @=? True+      , PH.testCase "T" $ IPv4.reserved (IPv4.ipv4 192 0 1 0) @=? False+      ]     ]   , testGroup "IPv6 Range Operations"     [ testProperty "Idempotence of normalizing IPv6 range"