packages feed

IPv6Addr 0.7.0 → 1.0.0

raw patch · 5 files changed

+395/−438 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Text.IPv6Addr: instance Data.Aeson.Types.FromJSON.FromJSON Text.IPv6Addr.Types.IPv6Addr
- Text.IPv6Addr: instance Data.Aeson.Types.ToJSON.ToJSON Text.IPv6Addr.Types.IPv6Addr
- Text.IPv6Addr: instance GHC.Classes.Eq Text.IPv6Addr.Types.IPv6Addr
- Text.IPv6Addr.Manip: getTokIPv6AddrOf :: String -> IO (Maybe [IPv6AddrToken])
- Text.IPv6Addr.Manip: getTokMacAddrOf :: String -> IO (Maybe [IPv6AddrToken])
- Text.IPv6Addr.Manip: macAddrToIPv6AddrTokens :: Text -> Maybe [IPv6AddrToken]
- Text.IPv6Addr.Manip: randIPv6AddrChunk :: String -> IO IPv6AddrToken
- Text.IPv6Addr.Manip: randPartialIPv6Addr :: Int -> IO [IPv6AddrToken]
- Text.IPv6Addr.Types: AllZeros :: IPv6AddrToken
- Text.IPv6Addr.Types: Colon :: IPv6AddrToken
- Text.IPv6Addr.Types: DoubleColon :: IPv6AddrToken
- Text.IPv6Addr.Types: IPv4Addr :: !Text -> IPv6AddrToken
- Text.IPv6Addr.Types: IPv6Addr :: !Text -> IPv6Addr
- Text.IPv6Addr.Types: SixteenBit :: !Text -> IPv6AddrToken
- Text.IPv6Addr.Types: data IPv6Addr
- Text.IPv6Addr.Types: data IPv6AddrToken
- Text.IPv6Addr.Types: instance GHC.Classes.Eq Text.IPv6Addr.Types.IPv6AddrToken
- Text.IPv6Addr.Types: instance GHC.Show.Show Text.IPv6Addr.Types.IPv6Addr
- Text.IPv6Addr.Types: instance GHC.Show.Show Text.IPv6Addr.Types.IPv6AddrToken
+ Text.IPv6Addr: getTokIPv6AddrOf :: String -> IO (Maybe [IPv6AddrToken])
+ Text.IPv6Addr: getTokMacAddrOf :: String -> IO (Maybe [IPv6AddrToken])
+ Text.IPv6Addr: instance Data.Aeson.Types.FromJSON.FromJSON Text.IPv6Addr.IPv6Addr
+ Text.IPv6Addr: instance Data.Aeson.Types.ToJSON.ToJSON Text.IPv6Addr.IPv6Addr
+ Text.IPv6Addr: instance GHC.Classes.Eq Text.IPv6Addr.IPv6Addr
+ Text.IPv6Addr: instance GHC.Classes.Eq Text.IPv6Addr.IPv6AddrToken
+ Text.IPv6Addr: instance GHC.Show.Show Text.IPv6Addr.IPv6Addr
+ Text.IPv6Addr: instance GHC.Show.Show Text.IPv6Addr.IPv6AddrToken
+ Text.IPv6Addr: macAddrToIPv6AddrTokens :: Text -> Maybe [IPv6AddrToken]
+ Text.IPv6Addr: randIPv6AddrChunk :: String -> IO IPv6AddrToken
+ Text.IPv6Addr: randPartialIPv6Addr :: Int -> IO [IPv6AddrToken]

Files

IPv6Addr.cabal view
@@ -1,5 +1,5 @@ name:                IPv6Addr-version:             0.7.0+version:             1.0.0 synopsis:            Library to deal with IPv6 address text representations. description:         Library to deal with IPv6 address text representations, canonization and manipulations. homepage:            https://github.com/MichelBoucey/IPv6Addr@@ -19,9 +19,6 @@  library   exposed-modules:  Text.IPv6Addr-                  , Text.IPv6Addr.Manip-                  , Text.IPv6Addr.Types-  other-modules:    Text.IPv6Addr.Internal   other-extensions: OverloadedStrings   build-depends:    base >=4.8 && < 5                   , text >=1.1 && < 1.3
Text/IPv6Addr.hs view
@@ -1,9 +1,7 @@ {-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}  module Text.IPv6Addr-    (-      IPv6Addr (IPv6Addr)+    ( IPv6Addr (IPv6Addr)     , maybeIPv6Addr     , maybePureIPv6Addr     , maybeFullIPv6Addr@@ -16,26 +14,48 @@     , toIP6ARPA     , toUNC -    -- * Utils+    -- * Utilities     , getIPv6AddrOf     , randIPv6Addr     , randIPv6AddrWithPrefix -    ) where+    -- * Manipulations+    , randIPv6AddrChunk+    , randPartialIPv6Addr+    , macAddrToIPv6AddrTokens+    , getTokIPv6AddrOf+    , getTokMacAddrOf ) where +import           Control.Applicative  ((<|>))+import           Control.Monad        (replicateM, guard) import           Data.Aeson-import           Data.IP                (IPv6)-import           Data.Maybe             (fromJust, isNothing)-import           Data.Monoid            ((<>))-import qualified Data.Text              as T-import           Control.Monad          (guard)-import           Network                (HostName)-import           System.Random          (randomRIO)+import           Data.Attoparsec.Text as A+import           Data.Char            (intToDigit, isDigit)+import           Data.IP              (IPv6)+import           Data.List            (elemIndex, elemIndices, group,+                                       intersperse, isSuffixOf)+import           Data.Maybe           (fromJust, isNothing)+import           Data.Monoid          ((<>))+import qualified Data.Text            as T+import qualified Data.Text.Read       as R (decimal)+import           Network              (HostName)+import           Network.Info+import           Numeric              (showHex)+import           System.Random        (randomRIO) -import           Text.IPv6Addr.Internal-import           Text.IPv6Addr.Manip    (randPartialIPv6Addr)-import           Text.IPv6Addr.Types+data IPv6Addr = IPv6Addr !T.Text +instance Show IPv6Addr where+  show (IPv6Addr a) = T.unpack a++data IPv6AddrToken+  = SixteenBit  !T.Text -- ^ A four hexadecimal digits group representing a 16-Bit chunk+  | AllZeros            -- ^ An all zeros 16-Bit chunk+  | Colon               -- ^ A separator between 16-Bit chunks+  | DoubleColon         -- ^ A double-colon stands for a unique compression of many consecutive 16-Bit chunks+  | IPv4Addr    !T.Text -- ^ An embedded IPv4 address as representation of the last 32-Bit+  deriving (Eq, Show)+ instance Eq IPv6Addr where   (==) (IPv6Addr a) (IPv6Addr b) =     show (maybePureIPv6Addr a) == show (maybePureIPv6Addr b)@@ -111,7 +131,7 @@ toHostName = show  -- | Given an 'IPv6addr', returns the corresponding 'IPv6' address.-toIPv6 :: IPv6Addr -> IPv6+toIPv6 :: IPv6Addr -> Data.IP.IPv6 toIPv6 a = read $ show a  -- | Returns 'Just' the canonized 'IPv6Addr' of the given local network interface,@@ -172,9 +192,9 @@         Nothing  -> return Nothing   where   countChunks =-    foldr count (0,0)+    foldr go (0,0)     where-      count c (a,b) =+      go c (a,b) =         case c of           SixteenBit _ -> (a+1,b)           AllZeros     -> (a+1,b)@@ -185,4 +205,360 @@       SixteenBit _ -> ts ++ [Colon]       AllZeros     -> ts ++ [Colon]       _            -> ts+++-- ------------------------------------------------------------------------ --+-- Maniplations                                                             --+-- ------------------------------------------------------------------------ --++-- | Returns 'Just' a random 'SixteenBit' token based on a mask \"____\", each+-- underscore being replaced by a random hexadecimal digit.+--+-- > randIPv6AddrChunk "_f__" == Just (SixteenBit "bfd4")+--+randIPv6AddrChunk :: String -> IO IPv6AddrToken+randIPv6AddrChunk m =+  mapM getHex m >>= \g -> return $ SixteenBit $ T.dropWhile (=='0') $ T.pack g+  where+    getHex c+      | c == '_'  = getDigit+      | otherwise = pure c++-- | Generates a random partial 'IPv6Addr' with n 'SixteenBit'.+randPartialIPv6Addr :: Int -> IO [IPv6AddrToken]+randPartialIPv6Addr n =+  if n > 0 && n < 9+    then+      intersperse Colon <$>+        replicateM n (SixteenBit . T.pack <$> replicateM 4 getDigit)+    else pure []++-- | Given a MAC address, returns 'Just' the corresponding 'IPv6AddrToken' list, or 'Nothing'.+--+-- > macAddrToIPv6AddrTokens "fa:1d:58:cc:95:16" == Just [SixteenBit "fa1d",Colon,SixteenBit "58cc",Colon,SixteenBit "9516"]+--+macAddrToIPv6AddrTokens :: T.Text -> Maybe [IPv6AddrToken]+macAddrToIPv6AddrTokens t =+  case parse macAddr t of+    Done a b ->+      if a == T.empty+        then intersperse Colon <$> b+        else Nothing+    _        -> Nothing++--+-- Functions based upon Network.Info to get local MAC and IPv6 addresses.+--+-- | Given a valid name of a local network interface, returns 'Just' the list of+-- tokens of the interface's IPv6 address, or 'Nothing'.+--+-- > getTokIPv6AddrOf "eth0" == Just [SixteenBit "fe80",DoubleColon,SixteenBit "fa1d",Colon,SixteenBit "58cc",Colon,SixteenBit "9516"]+--+getTokIPv6AddrOf :: String -> IO (Maybe [IPv6AddrToken])+getTokIPv6AddrOf s =+  maybe Nothing (maybeTokIPv6Addr. T.pack . show) <$>+    (lookup s <$> networkInterfacesIPv6AddrList)++-- | Given a valid name of a local network interface,+-- returns 'Just' the corresponding list of 'IPv6AddrToken' of the interface's MAC Address,+-- or 'Nothing'.+--+-- > getTokMacAddrOf "eth0" == Just [SixteenBit "fa1d",Colon,SixteenBit "58cc",Colon,SixteenBit "9516"]+--+getTokMacAddrOf :: String -> IO (Maybe [IPv6AddrToken])+getTokMacAddrOf s =+  maybe Nothing (macAddrToIPv6AddrTokens . T.pack . show) <$>+    (lookup s <$> networkInterfacesMacAddrList)+  where+    networkInterfacesMacAddrList = getNetworkInterfaces >>=+      \n -> return (networkInterfacesMac <$> n)+      where networkInterfacesMac (NetworkInterface n _ _ m) = (n,m)++getDigit :: IO Char+getDigit = intToDigit <$> randomRIO (0,15)++-- ----------------------------------------------------------------------------+-- Internals+-- ----------------------------------------------------------------------------++-- | Returns the 'T.Text' of an IPv6 address.+fromIPv6Addr :: IPv6Addr -> T.Text+fromIPv6Addr (IPv6Addr t) = t++-- | Given an arbitrary list of 'IPv6AddrToken', returns the corresponding 'T.Text'.+ipv6TokensToText :: [IPv6AddrToken] -> T.Text+ipv6TokensToText l = T.concat $ map ipv6TokenToText l++-- | Returns the corresponding 'T.Text' of an IPv6 address token.+ipv6TokenToText :: IPv6AddrToken -> T.Text+ipv6TokenToText (SixteenBit s) = s+ipv6TokenToText Colon          = ":"+ipv6TokenToText DoubleColon    = "::"+ipv6TokenToText AllZeros       = "0" -- "A single 16-bit 0000 field MUST be represented as 0" (RFC 5952, 4.1)+ipv6TokenToText (IPv4Addr a)   = a++-- | Returns 'True' if a list of 'IPv6AddrToken' constitutes a valid IPv6 Address.+isIPv6Addr :: [IPv6AddrToken] -> Bool+isIPv6Addr [] = False+isIPv6Addr [DoubleColon] = True+isIPv6Addr [DoubleColon,SixteenBit "1"] = True+isIPv6Addr tks =+  diffNext tks && (do+    let cdctks = countDoubleColon tks+        lentks = length tks+        lasttk = last tks+        lenconst = (lentks == 15 && cdctks == 0) || (lentks < 15 && cdctks == 1)+    firstValidToken tks &&+      (case countIPv4Addr tks :: Int of+         0 -> case lasttk of+                SixteenBit _ -> lenconst+                DoubleColon  -> lenconst+                AllZeros     -> lenconst+                _            -> False+         1 -> case lasttk of+                IPv4Addr _ ->+                  (lentks == 13 && cdctks == 0) || (lentks < 12 && cdctks == 1)+                _          -> False+         _ -> False))+         where+           diffNext [] = False+           diffNext [_] = True+           diffNext (t:ts) = do+             let h = head ts+             case t of+               SixteenBit _ ->+                 case h of+                   SixteenBit _ -> False+                   AllZeros     -> False+                   _            -> diffNext ts+               AllZeros     ->+                 case h of+                   SixteenBit _ -> False+                   AllZeros     -> False+                   _            -> diffNext ts+               _            -> diffNext ts+           firstValidToken l =+             case head l of+               SixteenBit _ -> True+               DoubleColon  -> True+               AllZeros     -> True+               _            -> False+           countDoubleColon l = length $ elemIndices DoubleColon l++countIPv4Addr :: [IPv6AddrToken] -> Int+countIPv4Addr =+  foldr oneMoreIPv4Addr 0+  where+    oneMoreIPv4Addr t c =+      case t of+        IPv4Addr _ -> c + 1+        _          -> c++-- | This is the main function which returns 'Just' the list of a tokenized IPv6+-- address text representation validated against RFC 4291 and canonized+-- in conformation with RFC 5952, or 'Nothing'.+maybeTokIPv6Addr :: T.Text -> Maybe [IPv6AddrToken]+maybeTokIPv6Addr t =+  case maybeIPv6AddrTokens t of+    Just ltks -> do+      guard (isIPv6Addr ltks)+      Just $ (ipv4AddrReplacement . toDoubleColon . fromDoubleColon) ltks+    Nothing   -> Nothing+  where+    ipv4AddrReplacement ltks =+      if ipv4AddrRewrite ltks+        then init ltks ++ ipv4AddrToIPv6AddrTokens (last ltks)+        else ltks++-- | Returns 'Just' the list of tokenized pure IPv6 address, always rewriting an+-- embedded IPv4 address if present.+maybeTokPureIPv6Addr :: T.Text -> Maybe [IPv6AddrToken]+maybeTokPureIPv6Addr t = do+  ltks <- maybeIPv6AddrTokens t+  guard (isIPv6Addr ltks)+  return $ (toDoubleColon . ipv4AddrReplacement . fromDoubleColon) ltks+  where+    ipv4AddrReplacement ltks' =+      init ltks' ++ ipv4AddrToIPv6AddrTokens (last ltks')++-- | Tokenize a 'T.Text' into 'Just' a list of 'IPv6AddrToken', or 'Nothing'.+maybeIPv6AddrTokens :: T.Text -> Maybe [IPv6AddrToken]+maybeIPv6AddrTokens s =+  case readText s of+    Done r l  -> if r==T.empty then Just l else Nothing+    Fail {}   -> Nothing+    Partial _ -> Nothing+  where+    readText _s =+      feed+        (parse (many1 $ ipv4Addr <|> sixteenBit <|> doubleColon <|> colon) _s)+        T.empty++-- | An embedded IPv4 address have to be rewritten to output a pure IPv6 Address+-- text representation in hexadecimal digits. But some well-known prefixed IPv6+-- addresses have to keep visible in their text representation the fact that+-- they deals with IPv4 to IPv6 transition process (RFC 5952 Section 5):+--+-- IPv4-compatible IPv6 address like "::1.2.3.4"+--+-- IPv4-mapped IPv6 address like "::ffff:1.2.3.4"+--+-- IPv4-translated address like "::ffff:0:1.2.3.4"+--+-- IPv4-translatable address like "64:ff9b::1.2.3.4"+--+-- ISATAP address like "fe80::5efe:1.2.3.4"+--+ipv4AddrRewrite :: [IPv6AddrToken] -> Bool+ipv4AddrRewrite tks =+  case last tks of+    IPv4Addr _ -> do+      let itks = init tks+      not  (itks == [DoubleColon]+         || itks == [DoubleColon,SixteenBit tokffff,Colon]+         || itks == [DoubleColon,SixteenBit tokffff,Colon,AllZeros,Colon]+         || itks == [SixteenBit "64",Colon,SixteenBit "ff9b",DoubleColon]+         || [SixteenBit "200",Colon,SixteenBit tok5efe,Colon] `isSuffixOf` itks+         || [AllZeros,Colon,SixteenBit tok5efe,Colon] `isSuffixOf` itks+         || [DoubleColon,SixteenBit tok5efe,Colon] `isSuffixOf` itks)+    _          -> False+  where+    tokffff = "ffff"+    tok5efe = "5efe"++-- | Rewrites an embedded 'IPv4Addr' into the corresponding list of pure 'IPv6Addr' tokens.+--+-- > ipv4AddrToIPv6AddrTokens (IPv4Addr "127.0.0.1") == [SixteenBits "7f0",Colon,SixteenBits "1"]+--+ipv4AddrToIPv6AddrTokens :: IPv6AddrToken -> [IPv6AddrToken]+ipv4AddrToIPv6AddrTokens t =+  case t of+    IPv4Addr a -> do+      let m = toHex a+      [  SixteenBit ((!!) m 0 <> addZero ((!!) m 1))+       , Colon+       , SixteenBit ((!!) m 2 <> addZero ((!!) m 3)) ]+    _          -> [t]+    where+      toHex a = map (\x -> T.pack $ showHex (read (T.unpack x)::Int) "") $ T.split (=='.') a+      addZero d = if T.length d == 1 then "0" <> d else d++expandTokens :: [IPv6AddrToken] -> [IPv6AddrToken]+expandTokens =+  map expandToken+  where+    expandToken (SixteenBit s) = SixteenBit $ T.justifyRight 4 '0' s+    expandToken AllZeros       = SixteenBit "0000"+    expandToken t              = t++fromDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]+fromDoubleColon tks =+  if DoubleColon `notElem` tks+    then tks+    else do+      let s = splitAt (fromJust $ elemIndex DoubleColon tks) tks+          fsts = fst s+          snds = if not (null (snd s)) then tail(snd s) else []+          fste = if null fsts then [] else fsts ++ [Colon]+          snde = if null snds then [] else Colon : snds+      fste ++ allZerosTokensReplacement(quantityOfAllZerosTokenToReplace tks) ++ snde+      where+        allZerosTokensReplacement x = intersperse Colon (replicate x AllZeros)+        quantityOfAllZerosTokenToReplace _x =+          ntks tks - foldl (\c _x -> if (_x /= DoubleColon) && (_x /= Colon) then c+1 else c) 0 _x+          where+            ntks _tks = if countIPv4Addr _tks == 1 then 7 else 8++toDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]+toDoubleColon tks =+  zerosToDoubleColon tks (zerosRunToReplace $ zerosRunsList tks)+  where+    -- No all zeros token, so no double colon replacement...+    zerosToDoubleColon ls (_,0) = ls+    -- "The symbol '::' MUST NOT be used to shorten just one 16-bit 0 field" (RFC 5952 4.2.2)+    zerosToDoubleColon ls (_,1) = ls+    zerosToDoubleColon ls (i,l) =+      let ls' = filter (/= Colon) ls+      in intersperse Colon (Prelude.take i ls') ++ [DoubleColon] ++ intersperse Colon (drop (i+l) ls')+    zerosRunToReplace t =+      let l = longestLengthZerosRun t+      in (firstLongestZerosRunIndex t l,l)+      where+        firstLongestZerosRunIndex x y = sum . snd . unzip $ Prelude.takeWhile (/=(True,y)) x+        longestLengthZerosRun x =+          maximum $ map longest x+          where+            longest _t =+              case _t of+                (True,i) -> i+                _        -> 0+    zerosRunsList x =+      map helper $ groupZerosRuns x+      where+        helper h = (head h == AllZeros, lh) where lh = length h+        groupZerosRuns = group . filter (/= Colon)++ipv6TokensToIPv6Addr :: [IPv6AddrToken] -> Maybe IPv6Addr+ipv6TokensToIPv6Addr l = Just $ IPv6Addr $ ipv6TokensToText l++networkInterfacesIPv6AddrList :: IO [(String,Network.Info.IPv6)]+networkInterfacesIPv6AddrList =+  fmap networkInterfacesIPv6Addr <$> getNetworkInterfaces+  where+    networkInterfacesIPv6Addr (NetworkInterface n _ a _) = (n,a)++macAddr :: Parser (Maybe [IPv6AddrToken])+macAddr = do+  n1 <- count 2 hexaChar <* ":"+  n2 <- count 2 hexaChar <* ":"+  n3 <- count 2 hexaChar <* ":"+  n4 <- count 2 hexaChar <* ":"+  n5 <- count 2 hexaChar <* ":"+  n6 <- count 2 hexaChar+  return $ maybeIPv6AddrTokens $ T.pack $ concat [n1,n2,n3,n4,n5,n6]++sixteenBit :: Parser IPv6AddrToken+sixteenBit = do+  r <- ipv6AddrFullChunk <|> count 3 hexaChar <|> count 2 hexaChar <|> count 1 hexaChar+  -- "Leading zeros MUST be suppressed" (RFC 5952, 4.1)+  let r' = T.dropWhile (=='0') $ T.pack r+  return $+    if T.null r'+      then AllZeros+      -- Hexadecimal digits MUST be in lowercase (RFC 5952 4.3)+      else SixteenBit $ T.toLower r'++ipv4Addr :: Parser IPv6AddrToken+ipv4Addr = do+  n1 <- manyDigits <* "."+  guard (n1 /= T.empty)+  n2 <- manyDigits <* "."+  guard (n2 /= T.empty)+  n3 <- manyDigits <* "."+  guard (n3 /= T.empty)+  n4 <- manyDigits+  guard (n4 /= T.empty)+  return $ IPv4Addr $ T.intercalate "." [n1,n2,n3,n4]+  where+    manyDigits = do+      ds <- takeWhile1 isDigit+      case R.decimal ds :: Either String (Integer, T.Text) of+        Right (n,_) -> return $ if n < 256 then T.pack $ show n else T.empty+        Left  _     -> return T.empty++doubleColon :: Parser IPv6AddrToken+doubleColon = do+  _ <- string "::"+  return DoubleColon++colon :: Parser IPv6AddrToken+colon = do+  _ <- string ":"+  return Colon++ipv6AddrFullChunk :: Parser String+ipv6AddrFullChunk = count 4 hexaChar++hexaChar :: Parser Char+hexaChar = satisfy (inClass "0-9a-fA-F") 
− Text/IPv6Addr/Internal.hs
@@ -1,314 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Text.IPv6Addr.Internal-    ( expandTokens-    , macAddr-    , maybeIPv6AddrTokens-    , ipv4AddrToIPv6AddrTokens-    , ipv6TokensToText-    , ipv6TokensToIPv6Addr-    , isIPv6Addr-    , maybeTokIPv6Addr-    , maybeTokPureIPv6Addr-    , toDoubleColon-    , fromDoubleColon-    , fromIPv6Addr-    , networkInterfacesIPv6AddrList-    ) where--import           Control.Applicative  ((<|>))-import           Control.Monad        (guard)-import           Data.Attoparsec.Text-import           Data.Char            (isDigit)-import           Data.List            (elemIndex, elemIndices, group,-                                       intersperse, isSuffixOf)-import           Data.Maybe           (fromJust)-import           Data.Monoid          ((<>))-import qualified Data.Text            as T-import qualified Data.Text.Read       as R (decimal)-import           Network.Info-import           Numeric              (showHex)--import           Text.IPv6Addr.Types---- | Returns the 'T.Text' of an IPv6 address.-fromIPv6Addr :: IPv6Addr -> T.Text-fromIPv6Addr (IPv6Addr t) = t---- | Given an arbitrary list of 'IPv6AddrToken', returns the corresponding 'T.Text'.-ipv6TokensToText :: [IPv6AddrToken] -> T.Text-ipv6TokensToText l = T.concat $ map ipv6TokenToText l---- | Returns the corresponding 'T.Text' of an IPv6 address token.-ipv6TokenToText :: IPv6AddrToken -> T.Text-ipv6TokenToText (SixteenBit s) = s-ipv6TokenToText Colon = ":"-ipv6TokenToText DoubleColon = "::"-ipv6TokenToText AllZeros = "0" -- "A single 16-bit 0000 field MUST be represented as 0" (RFC 5952, 4.1)-ipv6TokenToText (IPv4Addr a) = a---- | Returns 'True' if a list of 'IPv6AddrToken' constitutes a valid IPv6 Address.-isIPv6Addr :: [IPv6AddrToken] -> Bool-isIPv6Addr [] = False-isIPv6Addr [DoubleColon] = True-isIPv6Addr [DoubleColon,SixteenBit "1"] = True-isIPv6Addr tks =-  diffNext tks && (do-    let cdctks = countDoubleColon tks-        lentks = length tks-        lasttk = last tks-        lenconst = (lentks == 15 && cdctks == 0) || (lentks < 15 && cdctks == 1)-    firstValidToken tks &&-      (case countIPv4Addr tks :: Int of-         0 -> case lasttk of-                SixteenBit _ -> lenconst-                DoubleColon  -> lenconst-                AllZeros     -> lenconst-                _            -> False-         1 -> case lasttk of-                IPv4Addr _ ->-                  (lentks == 13 && cdctks == 0) || (lentks < 12 && cdctks == 1)-                _          -> False-         _ -> False))-         where-           diffNext [] = False-           diffNext [_] = True-           diffNext (t:ts) = do-             let h = head ts-             case t of-               SixteenBit _ ->-                 case h of-                   SixteenBit _ -> False-                   AllZeros     -> False-                   _            -> diffNext ts-               AllZeros     ->-                 case h of-                   SixteenBit _ -> False-                   AllZeros     -> False-                   _            -> diffNext ts-               _            -> diffNext ts-           firstValidToken l =-             case head l of-               SixteenBit _ -> True-               DoubleColon  -> True-               AllZeros     -> True-               _            -> False-           countDoubleColon l = length $ elemIndices DoubleColon l--countIPv4Addr :: [IPv6AddrToken] -> Int-countIPv4Addr =-  foldr oneMoreIPv4Addr 0-  where-    oneMoreIPv4Addr t c =-      case t of-        IPv4Addr _ -> c + 1-        _          -> c---- | This is the main function which returns 'Just' the list of a tokenized IPv6--- address text representation validated against RFC 4291 and canonized--- in conformation with RFC 5952, or 'Nothing'.-maybeTokIPv6Addr :: T.Text -> Maybe [IPv6AddrToken]-maybeTokIPv6Addr t =-  case maybeIPv6AddrTokens t of-    Just ltks -> do-      guard (isIPv6Addr ltks)-      Just $ (ipv4AddrReplacement . toDoubleColon . fromDoubleColon) ltks-    Nothing   -> Nothing-  where-    ipv4AddrReplacement ltks =-      if ipv4AddrRewrite ltks-        then init ltks ++ ipv4AddrToIPv6AddrTokens (last ltks)-        else ltks---- | Returns 'Just' the list of tokenized pure IPv6 address, always rewriting an--- embedded IPv4 address if present.-maybeTokPureIPv6Addr :: T.Text -> Maybe [IPv6AddrToken]-maybeTokPureIPv6Addr t = do-  ltks <- maybeIPv6AddrTokens t-  guard (isIPv6Addr ltks)-  return $ (toDoubleColon . ipv4AddrReplacement . fromDoubleColon) ltks-  where-    ipv4AddrReplacement ltks' =-      init ltks' ++ ipv4AddrToIPv6AddrTokens (last ltks')---- | Tokenize a 'T.Text' into 'Just' a list of 'IPv6AddrToken', or 'Nothing'.-maybeIPv6AddrTokens :: T.Text -> Maybe [IPv6AddrToken]-maybeIPv6AddrTokens s =-  case readText s of-    Done r l  -> if r==T.empty then Just l else Nothing-    Fail {}   -> Nothing-    Partial _ -> Nothing-  where-    readText _s =-      feed-        (parse (many1 $ ipv4Addr <|> sixteenBit <|> doubleColon <|> colon) _s)-        T.empty---- | An embedded IPv4 address have to be rewritten to output a pure IPv6 Address--- text representation in hexadecimal digits. But some well-known prefixed IPv6--- addresses have to keep visible in their text representation the fact that--- they deals with IPv4 to IPv6 transition process (RFC 5952 Section 5):------ IPv4-compatible IPv6 address like "::1.2.3.4"------ IPv4-mapped IPv6 address like "::ffff:1.2.3.4"------ IPv4-translated address like "::ffff:0:1.2.3.4"------ IPv4-translatable address like "64:ff9b::1.2.3.4"------ ISATAP address like "fe80::5efe:1.2.3.4"----ipv4AddrRewrite :: [IPv6AddrToken] -> Bool-ipv4AddrRewrite tks =-  case last tks of-    IPv4Addr _ -> do-      let itks = init tks-      not  (itks == [DoubleColon]-         || itks == [DoubleColon,SixteenBit tokffff,Colon]-         || itks == [DoubleColon,SixteenBit tokffff,Colon,AllZeros,Colon]-         || itks == [SixteenBit "64",Colon,SixteenBit "ff9b",DoubleColon]-         || [SixteenBit "200",Colon,SixteenBit tok5efe,Colon] `isSuffixOf` itks-         || [AllZeros,Colon,SixteenBit tok5efe,Colon] `isSuffixOf` itks-         || [DoubleColon,SixteenBit tok5efe,Colon] `isSuffixOf` itks)-    _          -> False-  where-    tokffff = "ffff"-    tok5efe = "5efe"---- | Rewrites an embedded 'IPv4Addr' into the corresponding list of pure 'IPv6Addr' tokens.------ > ipv4AddrToIPv6AddrTokens (IPv4Addr "127.0.0.1") == [SixteenBits "7f0",Colon,SixteenBits "1"]----ipv4AddrToIPv6AddrTokens :: IPv6AddrToken -> [IPv6AddrToken]-ipv4AddrToIPv6AddrTokens t =-  case t of-    IPv4Addr a -> do-      let m = toHex a-      [  SixteenBit ((!!) m 0 <> addZero ((!!) m 1))-       , Colon-       , SixteenBit ((!!) m 2 <> addZero ((!!) m 3)) ]-    _          -> [t]-    where-      toHex a = map (\x -> T.pack $ showHex (read (T.unpack x)::Int) "") $ T.split (=='.') a-      addZero d = if T.length d == 1 then "0" <> d else d--expandTokens :: [IPv6AddrToken] -> [IPv6AddrToken]-expandTokens =-  map expandToken-  where-    expandToken (SixteenBit s) = SixteenBit $ T.justifyRight 4 '0' s-    expandToken AllZeros = SixteenBit "0000"-    expandToken t = t--fromDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]-fromDoubleColon tks =-  if DoubleColon `notElem` tks-    then tks-    else do-      let s = splitAt (fromJust $ elemIndex DoubleColon tks) tks-          fsts = fst s-          snds = if not (null (snd s)) then tail(snd s) else []-          fste = if null fsts then [] else fsts ++ [Colon]-          snde = if null snds then [] else Colon : snds-      fste ++ allZerosTokensReplacement(quantityOfAllZerosTokenToReplace tks) ++ snde-      where-        allZerosTokensReplacement x = intersperse Colon (replicate x AllZeros)-        quantityOfAllZerosTokenToReplace _x =-          ntks tks - foldl (\c _x -> if (_x /= DoubleColon) && (_x /= Colon) then c+1 else c) 0 _x-          where-            ntks _tks = if countIPv4Addr _tks == 1 then 7 else 8--toDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]-toDoubleColon tks =-  zerosToDoubleColon tks (zerosRunToReplace $ zerosRunsList tks)-  where-    -- No all zeros token, so no double colon replacement...-    zerosToDoubleColon ls (_,0) = ls-    -- "The symbol '::' MUST NOT be used to shorten just one 16-bit 0 field" (RFC 5952 4.2.2)-    zerosToDoubleColon ls (_,1) = ls-    zerosToDoubleColon ls (i,l) =-      let ls' = filter (/= Colon) ls-      in intersperse Colon (Prelude.take i ls') ++ [DoubleColon] ++ intersperse Colon (drop (i+l) ls')-    zerosRunToReplace t =-      let l = longestLengthZerosRun t-      in (firstLongestZerosRunIndex t l,l)-      where-        firstLongestZerosRunIndex x y = sum . snd . unzip $ Prelude.takeWhile (/=(True,y)) x-        longestLengthZerosRun x =-          maximum $ map longest x-          where-            longest _t =-              case _t of-                (True,i)  -> i-                _         -> 0-    zerosRunsList x =-      map helper $ groupZerosRuns x-      where-        helper h = (head h == AllZeros, lh) where lh = length h-        groupZerosRuns = group . filter (/= Colon)--ipv6TokensToIPv6Addr :: [IPv6AddrToken] -> Maybe IPv6Addr-ipv6TokensToIPv6Addr l = Just $ IPv6Addr $ ipv6TokensToText l--networkInterfacesIPv6AddrList :: IO [(String,IPv6)]-networkInterfacesIPv6AddrList =-  fmap networkInterfacesIPv6Addr <$> getNetworkInterfaces-  where-    networkInterfacesIPv6Addr (NetworkInterface n _ a _) = (n,a)--macAddr :: Parser (Maybe [IPv6AddrToken])-macAddr = do-  n1 <- count 2 hexaChar <* ":"-  n2 <- count 2 hexaChar <* ":"-  n3 <- count 2 hexaChar <* ":"-  n4 <- count 2 hexaChar <* ":"-  n5 <- count 2 hexaChar <* ":"-  n6 <- count 2 hexaChar-  return $ maybeIPv6AddrTokens $ T.pack $ concat [n1,n2,n3,n4,n5,n6]--sixteenBit :: Parser IPv6AddrToken-sixteenBit = do-  r <- ipv6AddrFullChunk <|> count 3 hexaChar <|> count 2 hexaChar <|> count 1 hexaChar-  -- "Leading zeros MUST be suppressed" (RFC 5952, 4.1)-  let r' = T.dropWhile (=='0') $ T.pack r-  return $-    if T.null r'-      then AllZeros-      -- Hexadecimal digits MUST be in lowercase (RFC 5952 4.3)-      else SixteenBit $ T.toLower r'--ipv4Addr :: Parser IPv6AddrToken-ipv4Addr = do-  n1 <- manyDigits <* "."-  guard (n1 /= T.empty)-  n2 <- manyDigits <* "."-  guard (n2 /= T.empty)-  n3 <- manyDigits <* "."-  guard (n3 /= T.empty)-  n4 <- manyDigits-  guard (n4 /= T.empty)-  return $ IPv4Addr $ T.intercalate "." [n1,n2,n3,n4]-  where-    manyDigits = do-      ds <- takeWhile1 isDigit-      case R.decimal ds :: Either String (Integer, T.Text) of-        Right (n,_) -> return $ if n < 256 then T.pack $ show n else T.empty-        Left  _     -> return T.empty--doubleColon :: Parser IPv6AddrToken-doubleColon = do-  _ <- string "::"-  return DoubleColon--colon :: Parser IPv6AddrToken-colon = do-  _ <- string ":"-  return Colon--ipv6AddrFullChunk :: Parser String-ipv6AddrFullChunk = count 4 hexaChar--hexaChar :: Parser Char-hexaChar = satisfy (inClass "0-9a-fA-F")-
− Text/IPv6Addr/Manip.hs
@@ -1,85 +0,0 @@-module Text.IPv6Addr.Manip-    ( randIPv6AddrChunk-    , randPartialIPv6Addr-    , macAddrToIPv6AddrTokens-    , getTokIPv6AddrOf-    , getTokMacAddrOf-    ) where--import           Control.Monad          (replicateM)-import           Data.Attoparsec.Text   as A-import           Data.Char              (intToDigit)-import           Data.List              (intersperse)-import qualified Data.Text              as T-import           Network.Info-import           System.Random          (randomRIO)--import           Text.IPv6Addr.Internal-import           Text.IPv6Addr.Types---- | Returns 'Just' a random 'SixteenBit' token based on a mask \"____\", each--- underscore being replaced by a random hexadecimal digit.------ > randIPv6AddrChunk "_f__" == Just (SixteenBit "bfd4")----randIPv6AddrChunk :: String -> IO IPv6AddrToken-randIPv6AddrChunk m =-  mapM getHex m >>= \g -> return $ SixteenBit $ T.dropWhile (=='0') $ T.pack g-  where-    getHex c-      | c == '_'  = getDigit-      | otherwise = pure c---- | Generates a random partial 'IPv6Addr' with n 'SixteenBit'.-randPartialIPv6Addr :: Int -> IO [IPv6AddrToken]-randPartialIPv6Addr n =-  if n > 0 && n < 9 -    then-      intersperse Colon <$>-        replicateM n (SixteenBit <$> T.pack <$> replicateM 4 getDigit)-    else pure []---- | Given a MAC address, returns 'Just' the corresponding 'IPv6AddrToken' list, or 'Nothing'.------ > macAddrToIPv6AddrTokens "fa:1d:58:cc:95:16" == Just [SixteenBit "fa1d",Colon,SixteenBit "58cc",Colon,SixteenBit "9516"]----macAddrToIPv6AddrTokens :: T.Text -> Maybe [IPv6AddrToken]-macAddrToIPv6AddrTokens t =-  case parse macAddr t of-    Done a b ->-      if a == T.empty-        then intersperse Colon <$> b-        else Nothing-    _        -> Nothing------- Functions based upon Network.Info to get local MAC and IPv6 addresses.------ | Given a valid name of a local network interface, returns 'Just' the list of--- tokens of the interface's IPv6 address, or 'Nothing'.------ > getTokIPv6AddrOf "eth0" == Just [SixteenBit "fe80",DoubleColon,SixteenBit "fa1d",Colon,SixteenBit "58cc",Colon,SixteenBit "9516"]----getTokIPv6AddrOf :: String -> IO (Maybe [IPv6AddrToken])-getTokIPv6AddrOf s =-  maybe Nothing (maybeTokIPv6Addr. T.pack . show) <$>-    (lookup s <$> networkInterfacesIPv6AddrList)---- | Given a valid name of a local network interface,--- returns 'Just' the corresponding list of 'IPv6AddrToken' of the interface's MAC Address,--- or 'Nothing'.------ > getTokMacAddrOf "eth0" == Just [SixteenBit "fa1d",Colon,SixteenBit "58cc",Colon,SixteenBit "9516"]----getTokMacAddrOf :: String -> IO (Maybe [IPv6AddrToken])-getTokMacAddrOf s =-  maybe Nothing (macAddrToIPv6AddrTokens . T.pack . show) <$>-    (lookup s <$> networkInterfacesMacAddrList)-  where-    networkInterfacesMacAddrList = getNetworkInterfaces >>=-      \n -> return (networkInterfacesMac <$> n)-      where networkInterfacesMac (NetworkInterface n _ _ m) = (n,m)--getDigit :: IO Char-getDigit = intToDigit <$> randomRIO (0,15)-
− Text/IPv6Addr/Types.hs
@@ -1,17 +0,0 @@-module Text.IPv6Addr.Types where--import qualified Data.Text as T--data IPv6Addr = IPv6Addr !T.Text--instance Show IPv6Addr where-  show (IPv6Addr a) = T.unpack a--data IPv6AddrToken-  = SixteenBit  !T.Text -- ^ A four hexadecimal digits group representing a 16-Bit chunk-  | AllZeros            -- ^ An all zeros 16-Bit chunk-  | Colon               -- ^ A separator between 16-Bit chunks-  | DoubleColon         -- ^ A double-colon stands for a unique compression of many consecutive 16-Bit chunks-  | IPv4Addr    !T.Text -- ^ An embedded IPv4 address as representation of the last 32-Bit-  deriving (Eq, Show)-