ip 0.8.1 → 0.8.3
raw patch · 12 files changed
+603/−63 lines, 12 filesdep +HUnitdep +test-framework-hunitPVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit, test-framework-hunit
API changes (from Hackage documentation)
+ Net.IPv4.Range: contains :: IPv4Range -> IPv4 -> Bool
+ Net.IPv4.Range.Text: builder :: IPv4Range -> Builder
+ Net.IPv4.Range.Text: decode :: Text -> Maybe IPv4Range
+ Net.IPv4.Range.Text: decodeEither :: Text -> Either String IPv4Range
+ Net.IPv4.Range.Text: encode :: IPv4Range -> Text
+ Net.IPv4.Range.Text: parser :: Parser IPv4Range
+ Net.IPv4.Range.Text: print :: IPv4Range -> IO ()
+ Net.IPv4.Text: reader :: Reader IPv4
+ Net.Internal: decodeIPv4TextEither :: Text -> Either String Word32
+ Net.Internal: decodeIPv4TextReader :: Reader Word32
+ Net.Internal: eitherToAesonParser :: Either String a -> Parser a
+ Net.Internal: ipOctetSizeErrorMsg :: String
+ Net.Internal: macToTextPreAllocated :: Word8 -> Bool -> Word16 -> Word32 -> Text
+ Net.Internal: putMac :: ByteString -> Int -> Int -> MArray s -> ST s ()
+ Net.Internal: stripDecimal :: Text -> Either String Text
+ Net.Internal: twoHexDigits :: ByteString
+ Net.Internal: twoHexDigitsLower :: ByteString
+ Net.Mac.Text: encodeWith :: MacEncoding -> Mac -> Text
+ Net.Types: MacEncoding :: {-# UNPACK #-} !Word8 -> {-# UNPACK #-} !Bool -> MacEncoding
+ Net.Types: [macEncodingSeparator] :: MacEncoding -> {-# UNPACK #-} !Word8
+ Net.Types: [macEncodingUpperCase] :: MacEncoding -> {-# UNPACK #-} !Bool
+ Net.Types: data MacEncoding
+ Net.Types: instance Data.Vector.Generic.Base.Vector Data.Vector.Unboxed.Base.Vector Net.Types.IPv4Range
+ Net.Types: instance Data.Vector.Generic.Base.Vector Data.Vector.Unboxed.Base.Vector Net.Types.Mac
+ Net.Types: instance Data.Vector.Generic.Mutable.Base.MVector Data.Vector.Unboxed.Base.MVector Net.Types.IPv4Range
+ Net.Types: instance Data.Vector.Generic.Mutable.Base.MVector Data.Vector.Unboxed.Base.MVector Net.Types.Mac
+ Net.Types: instance Data.Vector.Unboxed.Base.Unbox Net.Types.IPv4Range
+ Net.Types: instance Data.Vector.Unboxed.Base.Unbox Net.Types.Mac
+ Net.Types: instance GHC.Classes.Eq Net.Types.MacEncoding
+ Net.Types: instance GHC.Classes.Ord Net.Types.MacEncoding
+ Net.Types: instance GHC.Generics.Generic Net.Types.MacEncoding
+ Net.Types: instance GHC.Read.Read Net.Types.MacEncoding
+ Net.Types: instance GHC.Show.Show Net.Types.MacEncoding
Files
- ip.cabal +8/−2
- src/Net/IPv4/Range.hs +1/−0
- src/Net/IPv4/Range/Text.hs +42/−0
- src/Net/IPv4/Text.hs +6/−1
- src/Net/Internal.hs +136/−24
- src/Net/Mac/Text.hs +7/−2
- src/Net/Types.hs +246/−20
- test/ArbitraryInstances.hs +1/−2
- test/Bench.hs +10/−1
- test/IPv4DecodeText1.hs +55/−0
- test/IPv4DecodeText2.hs +48/−0
- test/Test.hs +43/−11
ip.cabal view
@@ -1,5 +1,5 @@ name: ip-version: 0.8.1+version: 0.8.3 synopsis: Library for IP and MAC addresses description: Please see README.md homepage: https://github.com/andrewthad/haskell-ip#readme@@ -20,6 +20,7 @@ Net.Mac.ByteString.Char8 Net.IPv4 Net.IPv4.Range+ Net.IPv4.Range.Text Net.IPv4.String Net.IPv4.Text Net.IPv4.ByteString.Char8@@ -34,7 +35,7 @@ , bytestring >= 0.10 && < 0.11 , vector >= 0.11 && < 0.12 , primitive- ghc-options: -Wall+ ghc-options: -Wall -O2 default-language: Haskell2010 test-suite test@@ -49,6 +50,8 @@ , QuickCheck , text , bytestring+ , HUnit+ , test-framework-hunit other-modules: ArbitraryInstances Naive@@ -77,10 +80,13 @@ , criterion , text , bytestring+ , attoparsec other-modules: Naive IPv4Text1 IPv4Text2+ IPv4DecodeText1+ IPv4DecodeText2 IPv4ByteString1 ghc-options: -Wall -O2 default-language: Haskell2010
src/Net/IPv4/Range.hs view
@@ -1,6 +1,7 @@ module Net.IPv4.Range ( -- * Range functions normalize+ , contains , member , lowerInclusive , upperInclusive
+ src/Net/IPv4/Range/Text.hs view
@@ -0,0 +1,42 @@+module Net.IPv4.Range.Text+ ( encode+ , decode+ , decodeEither+ , builder+ , parser+ , print+ ) where++import Prelude hiding (print)+import Net.Types (IPv4Range(..),IPv4(..))+import Data.Text (Text)+import Data.Word (Word8, Word32)+import qualified Data.Text.IO as Text+import qualified Data.Attoparsec.Text as AT+import qualified Data.Text.Lazy.Builder as TBuilder+import qualified Net.Internal as Internal++encode :: IPv4Range -> Text+encode (IPv4Range (IPv4 w) r) = Internal.rangeToDotDecimalText w r++decodeEither :: Text -> Either String IPv4Range+decodeEither = Internal.rangeFromDotDecimalText' mkIPv4Range++decode :: Text -> Maybe IPv4Range+decode = Internal.rightToMaybe . decodeEither++builder :: IPv4Range -> TBuilder.Builder+builder (IPv4Range (IPv4 w) r) = Internal.rangeToDotDecimalBuilder w r++parser :: AT.Parser IPv4Range+parser = Internal.dotDecimalRangeParser mkIPv4Range++-- | This exists mostly for testing purposes.+print :: IPv4Range -> IO ()+print = Text.putStrLn . encode++-- internal function+mkIPv4Range :: Word32 -> Word8 -> IPv4Range+mkIPv4Range w = IPv4Range (IPv4 w)+{-# INLINE mkIPv4Range #-}+
src/Net/IPv4/Text.hs view
@@ -3,6 +3,7 @@ , decode , decodeEither , builder+ , reader , parser , print ) where@@ -12,6 +13,7 @@ import Net.IPv4 import Data.Text (Text) import Data.Coerce (coerce)+import qualified Data.Text.Read as Text (Reader) import qualified Data.Text.IO as Text import qualified Data.Attoparsec.Text as AT import qualified Data.Text.Lazy.Builder as TBuilder@@ -21,13 +23,16 @@ encode = Internal.toDotDecimalText . getIPv4 decodeEither :: Text -> Either String IPv4-decodeEither = coerce . Internal.fromDotDecimalText'+decodeEither = coerce . Internal.decodeIPv4TextEither decode :: Text -> Maybe IPv4 decode = Internal.rightToMaybe . decodeEither builder :: IPv4 -> TBuilder.Builder builder = Internal.toDotDecimalBuilder . getIPv4++reader :: Text.Reader IPv4+reader = coerce Internal.decodeIPv4TextReader parser :: AT.Parser IPv4 parser = coerce Internal.dotDecimalParser
src/Net/Internal.hs view
@@ -7,6 +7,8 @@ import Data.Text.Internal (Text(..)) import Data.ByteString (ByteString) import Data.Text.Lazy.Builder.Int (decimal)+import Control.Monad+import qualified Data.Text as Text import qualified Data.Text.Lazy as LText import qualified Data.Attoparsec.Text as AT import qualified Data.Aeson.Types as Aeson@@ -15,8 +17,14 @@ 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.Read as TextRead import qualified Data.Text.Lazy.Builder.Int as TBuilder +eitherToAesonParser :: Either String a -> Aeson.Parser a+eitherToAesonParser x = case x of+ Left err -> fail err+ Right a -> return a+ attoparsecParseJSON :: AT.Parser a -> Aeson.Value -> Aeson.Parser a attoparsecParseJSON p v = case v of@@ -26,6 +34,38 @@ Right res -> return res _ -> fail "expected a String" +stripDecimal :: Text -> Either String Text+stripDecimal t = case Text.uncons t of+ Nothing -> Left "expected a dot but input ended instead"+ Just (c,tnext) -> if c == '.'+ then Right tnext+ else Left "expected a dot but found a different character"+{-# INLINE stripDecimal #-}++decodeIPv4TextReader :: TextRead.Reader Word32+decodeIPv4TextReader t1' = do+ (a,t2) <- TextRead.decimal t1'+ t2' <- stripDecimal t2+ (b,t3) <- TextRead.decimal t2'+ t3' <- stripDecimal t3+ (c,t4) <- TextRead.decimal t3'+ t4' <- stripDecimal t4+ (d,t5) <- TextRead.decimal t4'+ if a > 255 || b > 255 || c > 255 || d > 255+ then Left ipOctetSizeErrorMsg+ else Right (fromOctets' a b c d,t5)+{-# INLINE decodeIPv4TextReader #-}++decodeIPv4TextEither :: Text -> Either String Word32+decodeIPv4TextEither t = case decodeIPv4TextReader t of+ Left err -> Left err+ Right (w,t') -> if Text.null t'+ then Right w+ else Left "expected end of text but it continued instead"++ipOctetSizeErrorMsg :: String+ipOctetSizeErrorMsg = "All octets in an IPv4 address must be between 0 and 255"+ rightToMaybe :: Either a b -> Maybe b rightToMaybe = either (const Nothing) Just @@ -95,6 +135,40 @@ get2 = fromIntegral . ByteString.unsafeIndex twoDigits get3 = fromIntegral . ByteString.unsafeIndex threeDigits +putMac :: ByteString -> Int -> Int -> TArray.MArray s -> ST s ()+putMac hexPairs pos w marr = do+ let i = w + w+ TArray.unsafeWrite marr pos $ fromIntegral $ ByteString.unsafeIndex hexPairs i+ TArray.unsafeWrite marr (pos + 1) $ fromIntegral $ ByteString.unsafeIndex hexPairs (i + 1)+{-# INLINE putMac #-}++macToTextPreAllocated :: Word8 -> Bool -> Word16 -> Word32 -> Text+macToTextPreAllocated separator' isUpperCase wa wb =+ let w1 = fromIntegral $ 255 .&. shiftR wa 8+ w2 = fromIntegral $ 255 .&. wa+ w3 = fromIntegral $ 255 .&. shiftR wb 24+ w4 = fromIntegral $ 255 .&. shiftR wb 16+ w5 = fromIntegral $ 255 .&. shiftR wb 8+ w6 = fromIntegral $ 255 .&. wb+ hexPairs = if isUpperCase then twoHexDigits else twoHexDigitsLower+ separator = fromIntegral separator' :: Word16+ arr = runST $ do+ marr <- TArray.new 17+ putMac hexPairs 0 w1 marr+ TArray.unsafeWrite marr 2 separator+ putMac hexPairs 3 w2 marr+ TArray.unsafeWrite marr 5 separator+ putMac hexPairs 6 w3 marr+ TArray.unsafeWrite marr 8 separator+ putMac hexPairs 9 w4 marr+ TArray.unsafeWrite marr 11 separator+ putMac hexPairs 12 w5 marr+ TArray.unsafeWrite marr 14 separator+ putMac hexPairs 15 w6 marr+ TArray.unsafeFreeze marr+ in Text arr 0 17+{-# INLINE macToTextPreAllocated #-}+ zero :: Word16 zero = 48 {-# INLINE zero #-}@@ -103,30 +177,7 @@ i2w v = zero + fromIntegral v {-# INLINE i2w #-} -twoDigits :: ByteString-twoDigits = BC8.pack- "0001020304050607080910111213141516171819\- \2021222324252627282930313233343536373839\- \4041424344454647484950515253545556575859\- \6061626364656667686970717273747576777879\- \8081828384858687888990919293949596979899" -threeDigits :: ByteString-threeDigits =- ByteString.replicate 300 0 <> BC8.pack- "100101102103104105106107108109110111112\- \113114115116117118119120121122123124125\- \126127128129130131132133134135136137138\- \139140141142143144145146147148149150151\- \152153154155156157158159160161162163164\- \165166167168169170171172173174175176177\- \178179180181182183184185186187188189190\- \191192193194195196197198199200201202203\- \204205206207208209210211212213214215216\- \217218219220221222223224225226227228229\- \230231232233234235236237238239240241242\- \243244245246247248249250251252253254255"- fromDotDecimalText' :: Text -> Either String Word32 fromDotDecimalText' t = AT.parseOnly (dotDecimalParser <* AT.endOfInput) t@@ -167,7 +218,7 @@ where limitSize i = if i > 255- then fail "All octets in an ip address must be between 0 and 255"+ then fail ipOctetSizeErrorMsg else return i -- | This is sort of a misnomer. It takes Word32 to make@@ -253,3 +304,64 @@ macFromText' f = AT.parseOnly (macTextParser f <* AT.endOfInput) {-# INLINE macFromText' #-} +twoDigits :: ByteString+twoDigits = BC8.pack+ "0001020304050607080910111213141516171819\+ \2021222324252627282930313233343536373839\+ \4041424344454647484950515253545556575859\+ \6061626364656667686970717273747576777879\+ \8081828384858687888990919293949596979899"++threeDigits :: ByteString+threeDigits =+ ByteString.replicate 300 0 <> BC8.pack+ "100101102103104105106107108109110111112\+ \113114115116117118119120121122123124125\+ \126127128129130131132133134135136137138\+ \139140141142143144145146147148149150151\+ \152153154155156157158159160161162163164\+ \165166167168169170171172173174175176177\+ \178179180181182183184185186187188189190\+ \191192193194195196197198199200201202203\+ \204205206207208209210211212213214215216\+ \217218219220221222223224225226227228229\+ \230231232233234235236237238239240241242\+ \243244245246247248249250251252253254255"++twoHexDigits :: ByteString+twoHexDigits = BC8.pack+ "000102030405060708090A0B0C0D0E0F\+ \101112131415161718191A1B1C1D1E1F\+ \202122232425262728292A2B2C2D2E2F\+ \303132333435363738393A3B3C3D3E3F\+ \404142434445464748494A4B4C4D4E4F\+ \505152535455565758595A5B5C5D5E5F\+ \606162636465666768696A6B6C6D6E6F\+ \707172737475767778797A7B7C7D7E7F\+ \808182838485868788898A8B8C8D8E8F\+ \909192939495969798999A9B9C9D9E9F\+ \A0A1A2A3A4A5A6A7A8A9AAABACADAEAF\+ \B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF\+ \C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF\+ \D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF\+ \E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF\+ \F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"++twoHexDigitsLower :: ByteString+twoHexDigitsLower = BC8.pack+ "000102030405060708090a0b0c0d0e0f\+ \101112131415161718191a1b1c1d1e1f\+ \202122232425262728292a2b2c2d2e2f\+ \303132333435363738393a3b3c3d3e3f\+ \404142434445464748494a4b4c4d4e4f\+ \505152535455565758595a5b5c5d5e5f\+ \606162636465666768696a6b6c6d6e6f\+ \707172737475767778797a7b7c7d7e7f\+ \808182838485868788898a8b8c8d8e8f\+ \909192939495969798999a9b9c9d9e9f\+ \a0a1a2a3a4a5a6a7a8a9aaabacadaeaf\+ \b0b1b2b3b4b5b6b7b8b9babbbcbdbebf\+ \c0c1c2c3c4c5c6c7c8c9cacbcccdcecf\+ \d0d1d2d3d4d5d6d7d8d9dadbdcdddedf\+ \e0e1e2e3e4e5e6e7e8e9eaebecedeeef\+ \f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
src/Net/Mac/Text.hs view
@@ -4,9 +4,10 @@ , decodeEither , builder , parser+ , encodeWith ) where -import Net.Types (Mac(..))+import Net.Types (Mac(..),MacEncoding(..)) import Net.Mac (fromOctetsNoCast) import Data.Text (Text) import qualified Net.Internal as Internal@@ -14,7 +15,7 @@ import qualified Data.Text.Lazy.Builder as TBuilder encode :: Mac -> Text-encode (Mac a b) = Internal.macToText a b+encode (Mac a b) = Internal.macToTextPreAllocated 58 False a b decodeEither :: Text -> Either String Mac decodeEither = Internal.macFromText' fromOctetsNoCast@@ -27,4 +28,8 @@ parser :: AT.Parser Mac parser = Internal.macTextParser fromOctetsNoCast++encodeWith :: MacEncoding -> Mac -> Text+encodeWith (MacEncoding separator isUpperCase) (Mac a b) =+ Internal.macToTextPreAllocated separator isUpperCase a b
src/Net/Types.hs view
@@ -11,12 +11,15 @@ ( IPv4(..) , IPv4Range(..) , Mac(..)+ , MacEncoding(..) ) where import qualified Net.Internal as Internal import qualified Data.Aeson as Aeson import qualified Data.Aeson.Types as Aeson import qualified Data.Attoparsec.Text as AT+-- import qualified Data.Vector.Generic as G+-- import qualified Data.Vector.Generic.Mutable as M import qualified Data.Vector.Generic as GVector import qualified Data.Vector.Unboxed as UVector import qualified Data.Vector.Primitive as PVector@@ -46,14 +49,40 @@ , ipv4RangeLength :: {-# UNPACK #-} !Word8 } deriving (Eq,Ord,Show,Read,Generic) +-- | A 48-bit MAC address.+data Mac = Mac+ { macA :: {-# UNPACK #-} !Word16+ , macB :: {-# UNPACK #-} !Word32+ }+ deriving (Eq,Ord,Show,Read,Generic)++data MacEncoding = MacEncoding+ { macEncodingSeparator :: {-# UNPACK #-} !Word8 -- ^ ASCII value of the separator+ , macEncodingUpperCase :: {-# UNPACK #-} !Bool+ } deriving (Eq,Ord,Show,Read,Generic)++instance Hashable Mac++instance ToJSON Mac where+ toJSON (Mac a b) = Aeson.String (Internal.macToText a b)++instance FromJSON Mac where+ parseJSON = Internal.attoparsecParseJSON+ (Internal.macTextParser macFromOctets' <* AT.endOfInput)++macFromOctets' :: Word16 -> Word16 -> Word32 -> Word32 -> Word32 -> Word32 -> Mac+macFromOctets' a b c d e f = Mac+ ( shiftL a 8 .|. b )+ ( shiftL c 24 .|. shiftL d 16 .|. shiftL e 8 .|. f )+{-# INLINE macFromOctets' #-}+ instance Hashable IPv4Range instance ToJSON IPv4 where toJSON (IPv4 addr) = Aeson.String (Internal.toDotDecimalText addr) instance FromJSON IPv4 where- parseJSON = Internal.attoparsecParseJSON- (coerce (Internal.dotDecimalParser <* AT.endOfInput))+ parseJSON = Aeson.withText "IPv4" (Internal.eitherToAesonParser . coerce Internal.decodeIPv4TextEither) instance ToJSON IPv4Range where toJSON (IPv4Range (IPv4 addr) range) = Aeson.String (Internal.rangeToDotDecimalText addr range)@@ -116,23 +145,220 @@ basicUnsafeCopy (MV_IPv4 mv) (V_IPv4 v) = GVector.basicUnsafeCopy mv v elemseq _ = seq -data Mac = Mac- { macA :: {-# UNPACK #-} !Word16- , macB :: {-# UNPACK #-} !Word32- }- deriving (Eq,Ord,Show,Read,Generic)+data instance MUVector.MVector s IPv4Range+ = MV_IPv4Range {-# UNPACK #-} !Int !(MUVector.MVector s IPv4)+ !(MUVector.MVector s Word8)+data instance UVector.Vector IPv4Range+ = V_IPv4Range {-# UNPACK #-} !Int !(UVector.Vector IPv4)+ !(UVector.Vector Word8)+instance UVector.Unbox IPv4Range+instance MGVector.MVector MUVector.MVector IPv4Range where+ {-# INLINE basicLength #-}+ basicLength (MV_IPv4Range n_ as bs) = n_+ {-# INLINE basicUnsafeSlice #-}+ basicUnsafeSlice i_ m_ (MV_IPv4Range n_ as bs)+ = MV_IPv4Range m_ (MGVector.basicUnsafeSlice i_ m_ as)+ (MGVector.basicUnsafeSlice i_ m_ bs)+ {-# INLINE basicOverlaps #-}+ basicOverlaps (MV_IPv4Range n_1 as1 bs1) (MV_IPv4Range n_2 as2 bs2)+ = MGVector.basicOverlaps as1 as2+ || MGVector.basicOverlaps bs1 bs2+ {-# INLINE basicUnsafeNew #-}+ basicUnsafeNew n_+ = do+ as <- MGVector.basicUnsafeNew n_+ bs <- MGVector.basicUnsafeNew n_+ return $ MV_IPv4Range n_ as bs+ {-# INLINE basicInitialize #-}+ basicInitialize (MV_IPv4Range _ as bs)+ = do+ MGVector.basicInitialize as+ MGVector.basicInitialize bs+ {-# INLINE basicUnsafeReplicate #-}+ basicUnsafeReplicate n_ (IPv4Range a b)+ = do+ as <- MGVector.basicUnsafeReplicate n_ a+ bs <- MGVector.basicUnsafeReplicate n_ b+ return $ MV_IPv4Range n_ as bs+ {-# INLINE basicUnsafeRead #-}+ basicUnsafeRead (MV_IPv4Range n_ as bs) i_+ = do+ a <- MGVector.basicUnsafeRead as i_+ b <- MGVector.basicUnsafeRead bs i_+ return (IPv4Range a b)+ {-# INLINE basicUnsafeWrite #-}+ basicUnsafeWrite (MV_IPv4Range n_ as bs) i_ (IPv4Range a b)+ = do+ MGVector.basicUnsafeWrite as i_ a+ MGVector.basicUnsafeWrite bs i_ b+ {-# INLINE basicClear #-}+ basicClear (MV_IPv4Range n_ as bs)+ = do+ MGVector.basicClear as+ MGVector.basicClear bs+ {-# INLINE basicSet #-}+ basicSet (MV_IPv4Range n_ as bs) (IPv4Range a b)+ = do+ MGVector.basicSet as a+ MGVector.basicSet bs b+ {-# INLINE basicUnsafeCopy #-}+ basicUnsafeCopy (MV_IPv4Range n_1 as1 bs1) (MV_IPv4Range n_2 as2+ bs2)+ = do+ MGVector.basicUnsafeCopy as1 as2+ MGVector.basicUnsafeCopy bs1 bs2+ {-# INLINE basicUnsafeMove #-}+ basicUnsafeMove (MV_IPv4Range n_1 as1 bs1) (MV_IPv4Range n_2 as2+ bs2)+ = do+ MGVector.basicUnsafeMove as1 as2+ MGVector.basicUnsafeMove bs1 bs2+ {-# INLINE basicUnsafeGrow #-}+ basicUnsafeGrow (MV_IPv4Range n_ as bs) m_+ = do+ as' <- MGVector.basicUnsafeGrow as m_+ bs' <- MGVector.basicUnsafeGrow bs m_+ return $ MV_IPv4Range (m_+n_) as' bs'+instance GVector.Vector UVector.Vector IPv4Range where+ {-# INLINE basicUnsafeFreeze #-}+ basicUnsafeFreeze (MV_IPv4Range n_ as bs)+ = do+ as' <- GVector.basicUnsafeFreeze as+ bs' <- GVector.basicUnsafeFreeze bs+ return $ V_IPv4Range n_ as' bs'+ {-# INLINE basicUnsafeThaw #-}+ basicUnsafeThaw (V_IPv4Range n_ as bs)+ = do+ as' <- GVector.basicUnsafeThaw as+ bs' <- GVector.basicUnsafeThaw bs+ return $ MV_IPv4Range n_ as' bs'+ {-# INLINE basicLength #-}+ basicLength (V_IPv4Range n_ as bs) = n_+ {-# INLINE basicUnsafeSlice #-}+ basicUnsafeSlice i_ m_ (V_IPv4Range n_ as bs)+ = V_IPv4Range m_ (GVector.basicUnsafeSlice i_ m_ as)+ (GVector.basicUnsafeSlice i_ m_ bs)+ {-# INLINE basicUnsafeIndexM #-}+ basicUnsafeIndexM (V_IPv4Range n_ as bs) i_+ = do+ a <- GVector.basicUnsafeIndexM as i_+ b <- GVector.basicUnsafeIndexM bs i_+ return (IPv4Range a b)+ {-# INLINE basicUnsafeCopy #-}+ basicUnsafeCopy (MV_IPv4Range n_1 as1 bs1) (V_IPv4Range n_2 as2+ bs2)+ = do+ GVector.basicUnsafeCopy as1 as2+ GVector.basicUnsafeCopy bs1 bs2+ {-# INLINE elemseq #-}+ elemseq _ (IPv4Range a b)+ = GVector.elemseq (undefined :: UVector.Vector a) a+ . GVector.elemseq (undefined :: UVector.Vector b) b -instance Hashable Mac -instance ToJSON Mac where- toJSON (Mac a b) = Aeson.String (Internal.macToText a b)--instance FromJSON Mac where- parseJSON = Internal.attoparsecParseJSON- (Internal.macTextParser macFromOctets' <* AT.endOfInput)--macFromOctets' :: Word16 -> Word16 -> Word32 -> Word32 -> Word32 -> Word32 -> Mac-macFromOctets' a b c d e f = Mac- ( shiftL a 8 .|. b )- ( shiftL c 24 .|. shiftL d 16 .|. shiftL e 8 .|. f )-{-# INLINE macFromOctets' #-}+data instance MUVector.MVector s Mac+ = MV_Mac {-# UNPACK #-} !Int !(MUVector.MVector s Word16)+ !(MUVector.MVector s Word32)+data instance UVector.Vector Mac+ = V_Mac {-# UNPACK #-} !Int !(UVector.Vector Word16)+ !(UVector.Vector Word32)+instance UVector.Unbox Mac+instance MGVector.MVector MUVector.MVector Mac where+ {-# INLINE basicLength #-}+ basicLength (MV_Mac n_ as bs) = n_+ {-# INLINE basicUnsafeSlice #-}+ basicUnsafeSlice i_ m_ (MV_Mac n_ as bs)+ = MV_Mac m_ (MGVector.basicUnsafeSlice i_ m_ as)+ (MGVector.basicUnsafeSlice i_ m_ bs)+ {-# INLINE basicOverlaps #-}+ basicOverlaps (MV_Mac n_1 as1 bs1) (MV_Mac n_2 as2 bs2)+ = MGVector.basicOverlaps as1 as2+ || MGVector.basicOverlaps bs1 bs2+ {-# INLINE basicUnsafeNew #-}+ basicUnsafeNew n_+ = do+ as <- MGVector.basicUnsafeNew n_+ bs <- MGVector.basicUnsafeNew n_+ return $ MV_Mac n_ as bs+ {-# INLINE basicInitialize #-}+ basicInitialize (MV_Mac _ as bs)+ = do+ MGVector.basicInitialize as+ MGVector.basicInitialize bs+ {-# INLINE basicUnsafeReplicate #-}+ basicUnsafeReplicate n_ (Mac a b)+ = do+ as <- MGVector.basicUnsafeReplicate n_ a+ bs <- MGVector.basicUnsafeReplicate n_ b+ return $ MV_Mac n_ as bs+ {-# INLINE basicUnsafeRead #-}+ basicUnsafeRead (MV_Mac n_ as bs) i_+ = do+ a <- MGVector.basicUnsafeRead as i_+ b <- MGVector.basicUnsafeRead bs i_+ return (Mac a b)+ {-# INLINE basicUnsafeWrite #-}+ basicUnsafeWrite (MV_Mac n_ as bs) i_ (Mac a b)+ = do+ MGVector.basicUnsafeWrite as i_ a+ MGVector.basicUnsafeWrite bs i_ b+ {-# INLINE basicClear #-}+ basicClear (MV_Mac n_ as bs)+ = do+ MGVector.basicClear as+ MGVector.basicClear bs+ {-# INLINE basicSet #-}+ basicSet (MV_Mac n_ as bs) (Mac a b)+ = do+ MGVector.basicSet as a+ MGVector.basicSet bs b+ {-# INLINE basicUnsafeCopy #-}+ basicUnsafeCopy (MV_Mac n_1 as1 bs1) (MV_Mac n_2 as2 bs2)+ = do+ MGVector.basicUnsafeCopy as1 as2+ MGVector.basicUnsafeCopy bs1 bs2+ {-# INLINE basicUnsafeMove #-}+ basicUnsafeMove (MV_Mac n_1 as1 bs1) (MV_Mac n_2 as2 bs2)+ = do+ MGVector.basicUnsafeMove as1 as2+ MGVector.basicUnsafeMove bs1 bs2+ {-# INLINE basicUnsafeGrow #-}+ basicUnsafeGrow (MV_Mac n_ as bs) m_+ = do+ as' <- MGVector.basicUnsafeGrow as m_+ bs' <- MGVector.basicUnsafeGrow bs m_+ return $ MV_Mac (m_+n_) as' bs'+instance GVector.Vector UVector.Vector Mac where+ {-# INLINE basicUnsafeFreeze #-}+ basicUnsafeFreeze (MV_Mac n_ as bs)+ = do+ as' <- GVector.basicUnsafeFreeze as+ bs' <- GVector.basicUnsafeFreeze bs+ return $ V_Mac n_ as' bs'+ {-# INLINE basicUnsafeThaw #-}+ basicUnsafeThaw (V_Mac n_ as bs)+ = do+ as' <- GVector.basicUnsafeThaw as+ bs' <- GVector.basicUnsafeThaw bs+ return $ MV_Mac n_ as' bs'+ {-# INLINE basicLength #-}+ basicLength (V_Mac n_ as bs) = n_+ {-# INLINE basicUnsafeSlice #-}+ basicUnsafeSlice i_ m_ (V_Mac n_ as bs)+ = V_Mac m_ (GVector.basicUnsafeSlice i_ m_ as)+ (GVector.basicUnsafeSlice i_ m_ bs)+ {-# INLINE basicUnsafeIndexM #-}+ basicUnsafeIndexM (V_Mac n_ as bs) i_+ = do+ a <- GVector.basicUnsafeIndexM as i_+ b <- GVector.basicUnsafeIndexM bs i_+ return (Mac a b)+ {-# INLINE basicUnsafeCopy #-}+ basicUnsafeCopy (MV_Mac n_1 as1 bs1) (V_Mac n_2 as2 bs2)+ = do+ GVector.basicUnsafeCopy as1 as2+ GVector.basicUnsafeCopy bs1 bs2+ {-# INLINE elemseq #-}+ elemseq _ (Mac a b)+ = GVector.elemseq (undefined :: UVector.Vector a) a+ . GVector.elemseq (undefined :: UVector.Vector b) b
test/ArbitraryInstances.hs view
@@ -5,8 +5,7 @@ -- Orphan instances that are needed to make QuickCheck work. -import Net.Types (IPv4(..),IPv4Range(..))-import Net.Mac (Mac(..))+import Net.Types (IPv4(..),IPv4Range(..),Mac(..)) import Test.QuickCheck (Arbitrary(..)) deriving instance Arbitrary IPv4
test/Bench.hs view
@@ -9,29 +9,38 @@ import Data.Word import Data.ByteString (ByteString) import Control.Monad.ST+import qualified Data.Text as Text 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 as LText import qualified Data.Text.Lazy.Builder as TBuilder import qualified Data.Text.Array as TArray-import qualified Net.IPv4.Text as IPv4_Text+import qualified Net.IPv4.Text as IPv4_Text import qualified Naive import qualified IPv4Text1 import qualified IPv4Text2 import qualified IPv4ByteString1+import qualified IPv4DecodeText1+import qualified IPv4DecodeText2 import qualified Net.IPv4.ByteString.Char8 as NIPBS main :: IO () main = do let ipAddr = IPv4 1000000009+ ipText = Text.pack "192.168.5.99" defaultMain [ bgroup "IPv4 to Text" [ bench "Naive" $ whnf Naive.encodeText ipAddr , bench "Text Builder" $ whnf IPv4Text2.encode ipAddr , bench "Preallocated" $ whnf IPv4Text1.encode ipAddr+ ]+ , bgroup "IPv4 from Text"+ [ bench "Naive" $ whnf Naive.decodeText ipText+ , bench "Attoparsec" $ whnf IPv4DecodeText2.decodeText ipText+ , bench "Text Reader" $ whnf IPv4DecodeText1.decodeText ipText ] , bgroup "IPv4 to ByteString" [ bench "Naive" $ whnf Naive.encodeByteString ipAddr
+ test/IPv4DecodeText1.hs view
@@ -0,0 +1,55 @@+module IPv4DecodeText1 where++import Net.Types+import Data.Monoid ((<>))+import Data.Word+import Data.Bits ((.&.),(.|.),shiftR,shiftL,complement)+import Control.Monad.ST+import Data.Text.Internal (Text(..))+import Data.Text.Lazy.Builder.Int (decimal)+import Control.Monad+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText+import qualified Data.Attoparsec.Text as AT+import qualified Data.Text.Array as TArray+import qualified Data.Text.Lazy.Builder as TBuilder+import qualified Data.Text.Read as TextRead+import qualified Data.Text.Lazy.Builder.Int as TBuilder++stripDecimal :: Text -> Either String Text+stripDecimal t = case Text.uncons t of+ Nothing -> Left "expected a dot but input ended instead"+ Just (c,tnext) -> if c == '.'+ then Right tnext+ else Left "expected a dot but found a different character"+{-# INLINE stripDecimal #-}++decodeIPv4TextEither :: Text -> Either String Word32+decodeIPv4TextEither t1' = do+ (a,t2) <- TextRead.decimal t1'+ t2' <- stripDecimal t2+ (b,t3) <- TextRead.decimal t2'+ t3' <- stripDecimal t3+ (c,t4) <- TextRead.decimal t3'+ t4' <- stripDecimal t4+ (d,t5) <- TextRead.decimal t4'+ when (not (Text.null t5)) (Left "expected end of text but it continued instead")+ if a > 255 || b > 255 || c > 255 || d > 255+ then Left ipOctetSizeErrorMsg+ else Right (fromOctets' a b c d)++decodeText :: Text -> Maybe IPv4+decodeText t = case decodeIPv4TextEither t of+ Left _ -> Nothing+ Right w -> Just (IPv4 w)++ipOctetSizeErrorMsg :: String+ipOctetSizeErrorMsg = "All octets in an IPv4 address must be between 0 and 255"++fromOctets' :: Word32 -> Word32 -> Word32 -> Word32 -> Word32+fromOctets' a b c d =+ ( shiftL a 24+ .|. shiftL b 16+ .|. shiftL c 8+ .|. d+ )
+ test/IPv4DecodeText2.hs view
@@ -0,0 +1,48 @@+module IPv4DecodeText2 where++import Net.Types+import Data.Monoid ((<>))+import Data.Word+import Data.Bits ((.&.),(.|.),shiftR,shiftL,complement)+import Control.Monad.ST+import Data.Text.Internal (Text(..))+import Data.Text.Lazy.Builder.Int (decimal)+import Control.Monad+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText+import qualified Data.Attoparsec.Text as AT+import qualified Data.Text.Array as TArray+import qualified Data.Text.Lazy.Builder as TBuilder+import qualified Data.Text.Read as TextRead+import qualified Data.Text.Lazy.Builder.Int as TBuilder++dotDecimalParser :: AT.Parser Word32+dotDecimalParser = fromOctets'+ <$> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ where+ limitSize i =+ if i > 255+ then fail ipOctetSizeErrorMsg+ else return i++decodeText :: Text -> Maybe IPv4+decodeText t = case AT.parseOnly (dotDecimalParser <* AT.endOfInput) t of+ Left _ -> Nothing+ Right w -> Just (IPv4 w)++ipOctetSizeErrorMsg :: String+ipOctetSizeErrorMsg = "All octets in an IPv4 address must be between 0 and 255"++fromOctets' :: Word32 -> Word32 -> Word32 -> Word32 -> Word32+fromOctets' a b c d =+ ( shiftL a 24+ .|. shiftL b 16+ .|. shiftL c 8+ .|. d+ )
test/Test.hs view
@@ -5,15 +5,17 @@ import Test.QuickCheck (Gen, Arbitrary(..), choose) import Test.Framework (defaultMain, testGroup, Test) import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (Assertion,(@?=)) -import Net.Types (IPv4(..),IPv4Range(..))-import Net.Mac (Mac(..))+import Net.Types (IPv4(..),IPv4Range(..),Mac(..))+import qualified Data.Text as Text import qualified Net.IPv4 as IPv4 import qualified Net.IPv4.Range as IPv4Range-import qualified Net.IPv4.Text as IPv4_Text-import qualified Net.IPv4.ByteString.Char8 as IPv4_ByteString+import qualified Net.IPv4.Text as IPv4Text+import qualified Net.IPv4.ByteString.Char8 as IPv4ByteString import qualified Net.Mac as Mac-import qualified Net.Mac.Text as Mac_Text+import qualified Net.Mac.Text as MacText import ArbitraryInstances () import qualified Naive@@ -27,8 +29,18 @@ tests :: [Test] tests = [ testGroup "Encoding and Decoding"- [ testGroup "Naive IPv4 encode/decode"+ [ testGroup "Currently used IPv4 encode/decode" $ [ testProperty "Isomorphism"+ $ propEncodeDecodeIso IPv4Text.encode IPv4Text.decode+ , testCase "Decode an IP" testIPv4Decode+ ] ++ testDecodeFailures+ , testGroup "Currently used MAC Text encode/decode"+ [ testProperty "Isomorphism"+ $ propEncodeDecodeIso MacText.encode MacText.decode+ , testCase "Encode a MAC Address" testMacEncode+ ]+ , testGroup "Naive IPv4 encode/decode"+ [ testProperty "Isomorphism" $ propEncodeDecodeIso Naive.encodeText Naive.decodeText ] , testGroup "Text Builder IPv4 Text encode/decode"@@ -45,11 +57,7 @@ ] , 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+ $ propMatching IPv4ByteString.encode Naive.encodeByteString ] ] , testGroup "IP Range Operations"@@ -79,4 +87,28 @@ propRangeSelf :: IPv4Range -> Bool propRangeSelf r = IPv4Range.member (ipv4RangeBase r) r == True++testIPv4Decode :: Assertion+testIPv4Decode = IPv4Text.decode (Text.pack "124.222.255.0")+ @?= Just (IPv4.fromOctets 124 222 255 0)++textBadIPv4 :: [String]+textBadIPv4 =+ [ "122.256.0.0"+ , "1.1.1."+ , ".1.1.1."+ , ".1.1.1"+ , "1.1..1.1"+ , "1.9.x.2"+ , "1.9.3"+ , "1.9"+ ]++testDecodeFailures :: [Test]+testDecodeFailures = flip map textBadIPv4 $ \str ->+ testCase ("Should fail to decode [" ++ str ++ "]") $ IPv4Text.decode (Text.pack str) @?= Nothing++testMacEncode :: Assertion+testMacEncode = MacText.encode (Mac.fromOctets 0xFF 0x00 0xAB 0x12 0x99 0x0F)+ @?= Text.pack "ff:00:ab:12:99:0f"