ip 0.9.1 → 0.9.2
raw patch · 3 files changed
+92/−22 lines, 3 filesdep ~aesondep ~vector
Dependency ranges changed: aeson, vector
Files
- ip.cabal +1/−1
- src/Net/IPv6/Text.hs +38/−21
- test/Test.hs +53/−0
ip.cabal view
@@ -1,5 +1,5 @@ name: ip-version: 0.9.1+version: 0.9.2 synopsis: Library for IP and MAC addresses homepage: https://github.com/andrewthad/haskell-ip#readme license: BSD3
src/Net/IPv6/Text.hs view
@@ -1,18 +1,45 @@ {-# LANGUAGE BangPatterns #-} module Net.IPv6.Text- ( parser+ ( encode+ , parser ) where import Prelude hiding (print) import Net.Types (IPv6(..))+import Net.IPv6 (toWord16s) import Data.Bits+import Data.List (intercalate, group) import Data.Word import Control.Applicative+import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Attoparsec.Text as Atto-import Debug.Trace+import Numeric (showHex) +-- | Encodes the IP, using zero-compression on the leftmost-longest string of+-- zeroes in the address.+encode :: IPv6 -> Text+encode ip = toText [w1, w2, w3, w4, w5, w6, w7, w8]+ where+ (w1, w2, w3, w4, w5, w6, w7, w8) = toWord16s ip+ toText ws = Text.pack $ intercalate ":" $ expand 0 longestZ grouped+ where+ expand _ 8 _ = ["::"]+ expand _ _ [] = []+ expand i longest ((x, len):wsNext)+ -- zero-compressed group:+ | x == 0 && len == longest =+ -- first and last need an extra colon since there's nothing+ -- to concat against+ (if i == 0 || (i+len) == 8 then ":" else "")+ : expand (i+len) 0 wsNext+ -- normal group:+ | otherwise = replicate len (showHex x "") ++ expand (i+len) longest wsNext+ longestZ = maximum . (0:) . map snd . filter ((==0) . fst) $ grouped+ grouped = map (\x -> (head x, length x)) (group ws)++ parser :: Atto.Parser IPv6 parser = do s <- start@@ -41,27 +68,12 @@ _ <- Atto.anyChar -- should be a colon if colonIndex == currentIndex then fmap ResWord Atto.hexadecimal <|> pure ResDone- -- do- -- md <- Atto.peekChar- -- case md of- -- Nothing -> return ResDone- -- Just d -> fmap ResWord Atto.hexadecimal <|> pure- -- then return ResColon- -- else fmap ResWord Atto.hexadecimal else do d <- Atto.peekChar' if d == ':' then return ResColon else fmap ResWord Atto.hexadecimal- -- else if colonIndex == currentIndex- -- then fmap ResWord Atto.hexadecimal <|> (ResDone <$ Atto.anyChar)- -- else fmap ResWord Atto.hexadecimal else return ResDone- -- fmap ResWord Atto.hexadecimal <|> (ResColon <$ Atto.char ':')- -- ) <|> pure ResDone- -- r <- (do _ <- Atto.char ':'- -- fmap ResWord Atto.hexadecimal <|> (ResColon <$ Atto.char ':')- -- ) <|> pure ResDone case r of ResDone -> pure (S colonIndex currentIndex ws) ResColon -> if alreadySet colonIndex@@ -70,7 +82,12 @@ ResWord w -> restrictTo16 msg w >> go colonIndex (currentIndex + 1) (w : ws) toIPv6 :: S -> Maybe IPv6-toIPv6 (S colonIndex total input) = go 0 0 input+toIPv6 (S colonIndex total input) = case compare total 8 of+ EQ -> if colonIndex == (-1)+ then go 0 0 input+ else Nothing+ GT -> Nothing+ LT -> go 0 0 input where revColonIndex = total - colonIndex spacesToSkip = 8 - total@@ -111,9 +128,9 @@ -- The third is a reversed list of the 16s -- that comprise the ipv6 address. data S = S- { sDoubleColon :: {-# UNPACK #-} !Int- , sTotal :: {-# UNPACK #-} !Int- , sRevWords :: ![Word64]+ { _sDoubleColon :: {-# UNPACK #-} !Int+ , _sTotal :: {-# UNPACK #-} !Int+ , _sRevWords :: ![Word64] } deriving (Show,Read) data Res
test/Test.hs view
@@ -10,6 +10,7 @@ import Numeric (showHex) import Test.QuickCheck.Property (failed,succeeded,Result(..)) import Data.Word+import Data.Bifunctor import Net.Types (IPv4(..),IPv4Range(..),Mac(..),IPv6(..)) import qualified Data.Text as Text@@ -84,6 +85,8 @@ ] , testGroup "IPv6 encode/decode" [ testCase "Parser Test Cases" testIPv6Parser+ , testCase "Encode test cases" testIPv6Encode+ , testCase "Parser Failure Test Cases" testIPv6ParserFailure ] ] , testGroup "IP Range Operations"@@ -184,6 +187,56 @@ (IPv6Text.parser <* AT.endOfInput) (Text.pack str) )++testIPv6ParserFailure :: Assertion+testIPv6ParserFailure = do+ go "1111:2222:3333:4444:5555:6666::7777:8888"+ go "1111:2222:3333:4444:5555:6666:7777:8888:9999"+ go "1111:2222:3333:4444:5555:6666:7777:8888::9999"+ where+ go str =+ Left ()+ @?= bimap (\_ -> ()) HexIPv6+ (AT.parseOnly+ (IPv6Text.parser <* AT.endOfInput)+ (Text.pack str)+ )++testIPv6Encode :: Assertion+testIPv6Encode = do++ -- degenerate cases:+ "::" `roundTripsTo` "::"+ "1234::" `roundTripsTo` "1234::"+ "::1234" `roundTripsTo` "::1234"++ -- zero-compression works:+ "1234:1234:0000:0000:0000:0000:3456:3434" `roundTripsTo` "1234:1234::3456:3434"++ -- picks first case:+ "1234:0000:1234:0000:1234:0000:0123:1234" `roundTripsTo` "1234::1234:0:1234:0:123:1234"++ -- picks longest case:+ "1234:0000:1234:0000:0:0000:0123:1234" `roundTripsTo` "1234:0:1234::123:1234"++ -- can exclude all but first and last:+ "1234::1234" `roundTripsTo` "1234::1234"++ -- prefers leftmost part to zero-compress:+ "1:2:0:0:5::8" `roundTripsTo` "1:2::5:0:0:8"++ -- can work with no zeroes:+ "1:2:3:4:5:6:7:8" `roundTripsTo` "1:2:3:4:5:6:7:8"++ -- works with only first or last:+ "::2:3:4:5:6:7:8" `roundTripsTo` "::2:3:4:5:6:7:8"+ "1:2:3:4:5:6:7::" `roundTripsTo` "1:2:3:4:5:6:7::"++ where+ roundTripsTo s sExpected =+ case AT.parseOnly (IPv6Text.parser <* AT.endOfInput) (Text.pack s) of+ Right result -> IPv6Text.encode result @?= Text.pack sExpected+ Left failMsg -> fail failMsg -- parse shouldn't fail here textBadIPv4 :: [String] textBadIPv4 =