packages feed

ip 0.6.2 → 0.7

raw patch · 5 files changed

+271/−40 lines, 5 filesdep +doctestdep +primitivedep +vectordep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: doctest, primitive, vector

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Net.IPv4: instance Data.Vector.Generic.Mutable.Base.MVector Data.Vector.Unboxed.Base.MVector Net.IPv4.IPv4
+ Net.IPv4: lowerInclusive :: IPv4Range -> IPv4
+ Net.IPv4: mask :: Word8 -> Word32
+ Net.IPv4: member :: IPv4 -> IPv4Range -> Bool
+ Net.IPv4: normalize :: IPv4Range -> IPv4Range
+ Net.IPv4: prAddr :: IPv4 -> IO ()
+ Net.IPv4: prRange :: IPv4Range -> IO ()
+ Net.IPv4: private16 :: IPv4Range
+ Net.IPv4: private20 :: IPv4Range
+ Net.IPv4: private24 :: IPv4Range
+ Net.IPv4: upperInclusive :: IPv4Range -> IPv4
- Net.IPv4: IPv4Range :: {-# UNPACK #-} !IPv4 -> {-# UNPACK #-} !Int8 -> IPv4Range
+ Net.IPv4: IPv4Range :: {-# UNPACK #-} !IPv4 -> {-# UNPACK #-} !Word8 -> IPv4Range
- Net.IPv4: [ipv4RangeLength] :: IPv4Range -> {-# UNPACK #-} !Int8
+ Net.IPv4: [ipv4RangeLength] :: IPv4Range -> {-# UNPACK #-} !Word8

Files

ip.cabal view
@@ -1,5 +1,5 @@ name:                ip-version:             0.6.2+version:             0.7 synopsis:            Library for IP and MAC addresses description:         Please see README.md homepage:            https://github.com/andrewthad/haskell-ip#readme@@ -30,10 +30,12 @@     , hashable    >= 1.2  && < 1.3     , text        >= 1.2  && < 1.3     , bytestring  >= 0.10 && < 0.11+    , vector      >= 0.11 && < 0.12+    , primitive   ghc-options:         -Wall   default-language:    Haskell2010 -test-suite ip-test+test-suite test   type:                exitcode-stdio-1.0   hs-source-dirs:      test   main-is:             Test.hs@@ -46,11 +48,23 @@     , text     , bytestring   other-modules:+    ArbitraryInstances     Naive     IPv4Text1     IPv4Text2     IPv4ByteString1   ghc-options:         -Wall -O2+  default-language:    Haskell2010++test-suite doctest+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Doctests.hs+  build-depends:+      base+    , ip+    , doctest >= 0.10+    , QuickCheck   default-language:    Haskell2010  benchmark criterion
src/Net/IPv4.hs view
@@ -1,5 +1,9 @@- {-# LANGUAGE DeriveGeneric #-}- {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE ScopedTypeVariables #-}  {-| An IPv4 data type @@ -19,11 +23,24 @@   ( -- * Types     IPv4(..)   , IPv4Range(..)+    -- * Range functions+  , mask+  , normalize+  , member+  , lowerInclusive+  , upperInclusive+    -- * Private Ranges+  , private24+  , private20+  , private16     -- * Conversion Functions   , fromOctets   , fromOctets'   , toOctets-    -- * Encoding and Decoding Functions+    -- * Internal Functions+    -- $internal+  , prAddr+  , prRange   , fromDotDecimalText   , fromDotDecimalText'   , rangeFromDotDecimalText'@@ -36,6 +53,7 @@   ) where  import qualified Data.Text.Lazy         as LText+import qualified Data.Text.IO           as Text import qualified Data.Text.Lazy.Builder as TBuilder import Data.Text.Lazy.Builder.Int (decimal) import Data.Monoid ((<>))@@ -52,19 +70,38 @@ import Control.Monad import Data.Text.Internal (Text(..)) import Control.Monad.ST+import Data.Coerce (coerce)+import Unsafe.Coerce (unsafeCoerce) import Data.ByteString (ByteString)+import Data.Vector.Generic.Mutable      (MVector(..))+import Control.Monad.Primitive          (PrimMonad,PrimState)+import qualified Data.Vector.Unboxed    as UVector import qualified Data.ByteString.Char8  as BC8 import qualified Data.ByteString        as ByteString import qualified Data.ByteString.Unsafe as ByteString import qualified Data.Text.Lazy.Builder as TBuilder import qualified Data.Text.Array        as TArray +-- $setup+--+-- These are here to get doctest's property checking to work+--+-- >>> import Test.QuickCheck (Arbitrary(..))+-- >>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary }+-- >>> instance Arbitrary IPv4Range where { arbitrary = IPv4Range <$> arbitrary <*> arbitrary }+--++-- | A 32-bit Internet Protocol address. newtype IPv4 = IPv4 { getIPv4 :: Word32 }   deriving (Eq,Ord,Show,Read,Enum,Bounded,Hashable,Generic) +-- | The length should be between 0 and 32. These bounds are inclusive.+--   This expectation is not in any way enforced by this library because+--   it does not cause errors. A mask length greater than 32 will be+--   treated as if it were 32. data IPv4Range = IPv4Range   { ipv4RangeBase   :: {-# UNPACK #-} !IPv4-  , ipv4RangeLength :: {-# UNPACK #-} !Int8+  , ipv4RangeLength :: {-# UNPACK #-} !Word8   } deriving (Eq,Ord,Show,Read,Generic)  instance Hashable IPv4Range@@ -85,9 +122,119 @@       Right res -> return res   parseJSON _ = mzero --- mask :: Int -> IPv4--- mask w = IPv4 $ complement $ 0xffffffff `shiftR` w+newtype instance UVector.MVector s IPv4 = MV_IPv4 (UVector.MVector s Word32) +instance MVector UVector.MVector IPv4 where+  basicLength = coerce (basicLength :: UVector.MVector s Word32 -> Int)+  basicUnsafeSlice = coerce (basicUnsafeSlice :: Int -> Int -> UVector.MVector s Word32 -> UVector.MVector s Word32)+  basicInitialize :: forall m. PrimMonad m => UVector.MVector (PrimState m) IPv4 -> m ()+  basicInitialize = coerce (basicInitialize :: PrimMonad m => UVector.MVector (PrimState m) Word32 -> m ())+  basicUnsafeReplicate :: forall m. PrimMonad m => Int -> IPv4 -> m (UVector.MVector (PrimState m) IPv4)+  basicUnsafeReplicate i (IPv4 w) = fmap coerce (basicUnsafeReplicate i w :: m (UVector.MVector (PrimState m) Word32))+  basicUnsafeRead :: forall m. PrimMonad m => UVector.MVector (PrimState m) IPv4 -> Int -> m IPv4+  basicUnsafeRead v i = fmap coerce (basicUnsafeRead (coerce v :: UVector.MVector (PrimState m) Word32) i :: m Word32)+  basicUnsafeWrite :: forall m. PrimMonad m => UVector.MVector (PrimState m) IPv4 -> Int -> IPv4 -> m ()+  basicUnsafeWrite = coerce (basicUnsafeWrite :: UVector.MVector (PrimState m) Word32 -> Int -> Word32 -> m ())+  basicClear :: forall m. PrimMonad m => UVector.MVector (PrimState m) IPv4 -> m ()+  basicClear = coerce (basicClear :: UVector.MVector (PrimState m) Word32 -> m ())+  basicSet :: forall m. PrimMonad m => UVector.MVector (PrimState m) IPv4 -> IPv4 -> m ()+  basicSet = coerce (basicSet :: UVector.MVector (PrimState m) Word32 -> Word32 -> m ())+  basicUnsafeCopy :: forall m. PrimMonad m => UVector.MVector (PrimState m) IPv4 -> UVector.MVector (PrimState m) IPv4 -> m ()+  basicUnsafeCopy = coerce (basicUnsafeCopy :: UVector.MVector (PrimState m) Word32 -> UVector.MVector (PrimState m) Word32 -> m ())+  basicUnsafeMove :: forall m. PrimMonad m => UVector.MVector (PrimState m) IPv4 -> UVector.MVector (PrimState m) IPv4 -> m ()+  basicUnsafeMove = coerce (basicUnsafeMove :: UVector.MVector (PrimState m) Word32 -> UVector.MVector (PrimState m) Word32 -> m ())+  basicUnsafeGrow :: forall m. PrimMonad m => UVector.MVector (PrimState m) IPv4 -> Int -> m (UVector.MVector (PrimState m) IPv4)+  basicUnsafeGrow (MV_IPv4 v) i = fmap coerce (basicUnsafeGrow v i)++mask :: Word8 -> Word32+mask = complement . shiftR 0xffffffff . fromIntegral++-- normalizeInternal :: Word8 -> Word32 -> Word32+-- normalizeInternal len w = w .&. mask len++-- | Normalize an 'IPv4Range'. The first result of this is that the+-- 'IPv4' inside the 'IPv4Range' is changed so that the insignificant+-- bits are zeroed out. For example:+--+-- >>> prRange $ normalize $ IPv4Range (fromOctets 192 168 1 19) 24+-- 192.168.1.0/24+-- >>> prRange $ normalize $ IPv4Range (fromOctets 192 168 1 163) 28+-- 192.168.1.160/28+--+-- The second effect of this is that the mask length is lowered to+-- be 32 or smaller. Working with 'IPv4Range's that have not been+-- normalized does not cause any issues for this library, although+-- other applications may reject such ranges (especially those with+-- a mask length above 32).+--+-- Note that 'normalize' is idempotent, that is:+--+-- prop> normalize r == (normalize . normalize) r+normalize :: IPv4Range -> IPv4Range+normalize (IPv4Range (IPv4 w) len) =+  let len' = min len 32+      w' = w .&. mask len'+   in IPv4Range (IPv4 w') len'++-- | Checks to see if an 'IPv4' address belongs in the 'IPv4Range'.+--+-- >>> let ip = fromOctets 10 10 1 92+-- >>> contains (IPv4Range (fromOctets 10 0 0 0) 8) ip+-- True+-- >>> contains (IPv4Range (fromOctets 10 11 0 0) 16) ip+-- False+--+-- Typically, element-testing functions are written to take the element+-- as the first argument and the set as the second argument. This is intentionally+-- written the other way for better performance when iterating over a collection.+-- For example, you might test elements in a list for membership like this:+--+-- >>> let r = IPv4Range (fromOctets 10 10 10 6) 31+-- >>> mapM_ (print . contains r) (take 5 $ iterate succ $ fromOctets 10 10 10 5)+-- False+-- True+-- True+-- False+-- False+--+-- The implementation of 'contains' ensures that (with GHC), the bitmask+-- creation and range normalization only occur once in the above example.+-- They are reused as the list is iterated.+contains :: IPv4Range -> IPv4 -> Bool+contains (IPv4Range (IPv4 wsubnet) len) =+  let theMask = mask len+      wsubnetNormalized = wsubnet .&. theMask+   in \(IPv4 w) -> (w .&. theMask) == wsubnetNormalized++-- | This is provided to mirror the interface provided by @Data.Set@. It+-- behaves just like 'contains' but with flipped arguments.+--+-- prop> member ip r == contains r ip+member :: IPv4 -> IPv4Range -> Bool+member = flip contains++lowerInclusive :: IPv4Range -> IPv4+lowerInclusive (IPv4Range (IPv4 w) len) =+  IPv4 (w .&. mask len)++upperInclusive :: IPv4Range -> IPv4+upperInclusive (IPv4Range (IPv4 w) len) =+  let theInvertedMask = shiftR 0xffffffff (fromIntegral len)+      theMask = complement theInvertedMask+   in IPv4 ((w .&. theMask) .|. theInvertedMask)++-- | The RFC1918 24-bit block. Subnet mask: @10.0.0.0/8@+private24 :: IPv4Range+private24 = IPv4Range (fromOctets 10 0 0 0) 8++-- | The RFC1918 20-bit block. Subnet mask: @172.16.0.0/12@+private20 :: IPv4Range+private20  = IPv4Range (fromOctets 172 16 0 0) 12++-- | The RFC1918 16-bit block. Subnet mask: @192.168.0.0/16@+private16 :: IPv4Range+private16 = IPv4Range (fromOctets 192 168 0 0) 16+ fromDotDecimalText' :: Text -> Either String IPv4 fromDotDecimalText' t =   AT.parseOnly (dotDecimalParser <* AT.endOfInput) t@@ -130,12 +277,24 @@       then fail "All octets in an ip address must be between 0 and 255"       else return i +-- | Create an 'IPv4' address from four octets. The first argument+--   is the most significant octet. The last argument is the least+--   significant.+--+--   Since the 'Show' and 'Read' instances for 'IPv4' are not generally+--   usefully, this function is the recommened way to create 'IPv4' addresses.+--   For example:+--+--   >>> fromOctets 192 168 1 1+--   IPv4 {getIPv4 = 3232235777}+-- fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4 fromOctets a b c d = fromOctets'   (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)  -- | This is sort of a misnomer. It takes Word32 to make---   dotDecimalParser probably perform better.+--   dotDecimalParser probably perform better. This is mostly+--   for internal use. fromOctets' :: Word32 -> Word32 -> Word32 -> Word32 -> IPv4 fromOctets' a b c d = IPv4     ( shiftL a 24@@ -144,6 +303,9 @@   .|. d     ) +-- | Convert an 'IPv4' address into a quadruple of octets. The first+--   element in the quadruple is the most significant octet. The last+--   element is the least significant octet. toOctets :: IPv4 -> (Word8,Word8,Word8,Word8) toOctets (IPv4 w) =   ( fromIntegral (shiftR w 24)@@ -151,6 +313,20 @@   , fromIntegral (shiftR w 8)   , fromIntegral w   )++-- | $internal+-- Everything below here is not part of the stable API. Many of these+-- functions must live here because they are needed for the 'ToJSON' and+-- 'FromJSON' instances. Hopefully, at some point, these can be removed+-- from this module.++-- | This only exists for doctests. Do not use it.+prAddr :: IPv4 -> IO ()+prAddr = Text.putStrLn . toDotDecimalText++-- | This only exists for doctests. Do not use it.+prRange :: IPv4Range -> IO ()+prRange = Text.putStrLn . rangeToDotDecimalText  toDotDecimalText :: IPv4 -> Text toDotDecimalText = toTextPreAllocated
+ test/ArbitraryInstances.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE StandaloneDeriving         #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module ArbitraryInstances where++-- Orphan instances that are needed to make QuickCheck work.++import Net.IPv4 (IPv4(..),IPv4Range(..))+import Net.Mac (Mac(..))+import Test.QuickCheck (Arbitrary(..))++deriving instance Arbitrary IPv4++-- This instance can generate masks that exceed the recommended+-- length of 32.+instance Arbitrary IPv4Range where+  arbitrary = fmap fromTuple arbitrary+    where fromTuple (a,b) = IPv4Range a b++instance Arbitrary Mac where+  arbitrary = fmap fromTuple arbitrary+    where fromTuple (a,b) = Mac a b+
+ test/Doctests.hs view
@@ -0,0 +1,4 @@+import Test.DocTest++main :: IO ()+main = doctest ["src/Net/IPv4.hs"]
test/Test.hs view
@@ -1,6 +1,3 @@- {-# LANGUAGE StandaloneDeriving         #-}- {-# LANGUAGE GeneralizedNewtypeDeriving #-}- module Main (main) where  import Naive@@ -17,6 +14,7 @@ import qualified Net.Mac as Mac import qualified Net.Mac.Text as Mac_Text +import ArbitraryInstances () import qualified Naive import qualified IPv4Text1 import qualified IPv4Text2@@ -27,41 +25,57 @@  tests :: [Test] tests =-  [ testGroup "Naive IPv4 encode/decode"-    [ testProperty "Isomorphism"-        $ propEncodeDecodeIso Naive.encodeText Naive.decodeText-    ]-  , testGroup "Text Builder IPv4 Text encode/decode"-    [ testProperty "Identical to Naive"-        $ propMatching IPv4Text2.encode Naive.encodeText-    ]-  , testGroup "Raw byte array IPv4 Text encode/decode"-    [ testProperty "Identical to Naive"-        $ propMatching IPv4Text1.encode Naive.encodeText-    ]-  , testGroup "Raw byte array (without lookup table) IPv4 ByteString encode/decode"-    [ testProperty "Identical to Naive"-        $ propMatching IPv4ByteString1.encode Naive.encodeByteString-    ]-  , testGroup "Raw byte array (with lookup table) IPv4 ByteString encode/decode"-    [ testProperty "Identical to Naive"-        $ propMatching IPv4_ByteString.encode Naive.encodeByteString+  [ testGroup "Encoding and Decoding"+    [ testGroup "Naive IPv4 encode/decode"+      [ testProperty "Isomorphism"+          $ propEncodeDecodeIso Naive.encodeText Naive.decodeText+      ]+    , testGroup "Text Builder IPv4 Text encode/decode"+      [ testProperty "Identical to Naive"+          $ propMatching IPv4Text2.encode Naive.encodeText+      ]+    , testGroup "Raw byte array IPv4 Text encode/decode"+      [ testProperty "Identical to Naive"+          $ propMatching IPv4Text1.encode Naive.encodeText+      ]+    , testGroup "Raw byte array (without lookup table) IPv4 ByteString encode/decode"+      [ testProperty "Identical to Naive"+          $ propMatching IPv4ByteString1.encode Naive.encodeByteString+      ]+    , testGroup "Raw byte array (with lookup table) IPv4 ByteString encode/decode"+      [ testProperty "Identical to Naive"+          $ propMatching IPv4_ByteString.encode Naive.encodeByteString+      ]+    , testGroup "Raw byte array MAC Text encode/decode"+      [ testProperty "Isomorphism"+          $ propEncodeDecodeIso Mac_Text.encode Mac_Text.decode+      ]     ]-  , testGroup "Raw byte array MAC Text encode/decode"-    [ testProperty "Isomorphism"-        $ propEncodeDecodeIso Mac_Text.encode Mac_Text.decode+  , testGroup "IP Range Operations"+    [ testProperty "Idempotence of normalizing IPv4 range"+        $ propIdempotence IPv4.normalize+    , testProperty "Normalize does not affect membership" propNormalizeMember+    , testProperty "Membership agrees with bounds" propMemberUpperLower+    , testProperty "Range contains self" propRangeSelf     ]   ] -deriving instance Arbitrary IPv4--instance Arbitrary Mac where-  arbitrary = fmap fromTuple arbitrary-    where fromTuple (a,b) = Mac a b- propEncodeDecodeIso :: Eq a => (a -> b) -> (b -> Maybe a) -> a -> Bool propEncodeDecodeIso f g a = g (f a) == Just a  propMatching :: Eq b => (a -> b) -> (a -> b) -> a -> Bool propMatching f g a = f a == g a++propIdempotence :: Eq a => (a -> a) -> a -> Bool+propIdempotence f a = f a == f (f a)++propNormalizeMember :: IPv4 -> IPv4Range -> Bool+propNormalizeMember i r = IPv4.member r i == IPv4.member (IPv4.normalize r) i++propMemberUpperLower :: IPv4 -> IPv4Range -> Bool+propMemberUpperLower i r =+  (i >= IPv4.lowerInclusive r && i <= IPv4.upperInclusive r) == IPv4.member r i++propRangeSelf :: IPv4Range -> Bool+propRangeSelf r = IPv4.member r (IPv4.ipv4RangeBase r) == True