packages feed

IPv6Addr 0.1 → 0.2

raw patch · 6 files changed

+488/−390 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Text.IPv6Addr: AllZeros :: IPv6AddrToken
- Text.IPv6Addr: Colon :: IPv6AddrToken
- Text.IPv6Addr: DoubleColon :: IPv6AddrToken
- Text.IPv6Addr: IPv4Addr :: Text -> IPv6AddrToken
- Text.IPv6Addr: SixteenBits :: Text -> IPv6AddrToken
- Text.IPv6Addr: data IPv6AddrToken
- Text.IPv6Addr: getTokIPv6AddrOf :: String -> IO (Maybe [IPv6AddrToken])
- Text.IPv6Addr: getTokMacAddrOf :: String -> IO (Maybe [IPv6AddrToken])
- Text.IPv6Addr: instance Eq IPv4AddrToken
- Text.IPv6Addr: instance Eq IPv6AddrToken
- Text.IPv6Addr: instance Show IPv4AddrToken
- Text.IPv6Addr: instance Show IPv6AddrToken
- Text.IPv6Addr: ipv4AddrToIPv6AddrTokens :: IPv6AddrToken -> [IPv6AddrToken]
- Text.IPv6Addr: ipv6TokensToText :: [IPv6AddrToken] -> Text
- Text.IPv6Addr: isIPv6Addr :: [IPv6AddrToken] -> Bool
- Text.IPv6Addr: macAddrToIPv6AddrTokens :: Text -> [IPv6AddrToken]
- Text.IPv6Addr: maybeExpIPv6Addr :: Text -> Maybe IPv6Addr
- Text.IPv6Addr: maybeIPv6AddrTokens :: Text -> Maybe [IPv6AddrToken]
- Text.IPv6Addr: maybeTokIPv6Addr :: Text -> Maybe [IPv6AddrToken]
- Text.IPv6Addr: sixteenBitsRand :: String -> IO IPv6AddrToken
+ Text.IPv6Addr: maybeFullIPv6Addr :: Text -> Maybe IPv6Addr
+ Text.IPv6Addr: maybePureIPv6Addr :: Text -> Maybe IPv6Addr
+ Text.IPv6Addr.Internal: colon :: Text -> Maybe IPv6AddrToken
+ Text.IPv6Addr.Internal: doubleColon :: Text -> Maybe IPv6AddrToken
+ Text.IPv6Addr.Internal: expandTokens :: [IPv6AddrToken] -> [IPv6AddrToken]
+ Text.IPv6Addr.Internal: fromDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]
+ Text.IPv6Addr.Internal: ipv4Addr :: Text -> Maybe IPv6AddrToken
+ Text.IPv6Addr.Internal: ipv4AddrToIPv6AddrTokens :: IPv6AddrToken -> [IPv6AddrToken]
+ Text.IPv6Addr.Internal: ipv6TokensToText :: [IPv6AddrToken] -> Maybe Text
+ Text.IPv6Addr.Internal: isIPv6Addr :: [IPv6AddrToken] -> Bool
+ Text.IPv6Addr.Internal: maybeIPv6AddrToken :: Text -> Maybe IPv6AddrToken
+ Text.IPv6Addr.Internal: maybeIPv6AddrTokens :: Text -> Maybe [IPv6AddrToken]
+ Text.IPv6Addr.Internal: maybeTokIPv6Addr :: Text -> Maybe [IPv6AddrToken]
+ Text.IPv6Addr.Internal: maybeTokPureIPv6Addr :: Text -> Maybe [IPv6AddrToken]
+ Text.IPv6Addr.Internal: networkInterfacesIPv6AddrList :: IO [(String, IPv6)]
+ Text.IPv6Addr.Internal: sixteenBits :: Text -> Maybe IPv6AddrToken
+ Text.IPv6Addr.Internal: toDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]
+ Text.IPv6Addr.Manip: getTokIPv6AddrOf :: String -> IO (Maybe [IPv6AddrToken])
+ Text.IPv6Addr.Manip: getTokMacAddrOf :: String -> IO (Maybe [IPv6AddrToken])
+ Text.IPv6Addr.Manip: macAddrToIPv6AddrTokens :: Text -> [IPv6AddrToken]
+ Text.IPv6Addr.Manip: sixteenBitsArbToken :: String -> IO (Maybe IPv6AddrToken)
+ Text.IPv6Addr.Types: AllZeros :: IPv6AddrToken
+ Text.IPv6Addr.Types: Colon :: IPv6AddrToken
+ Text.IPv6Addr.Types: Dot :: IPv4AddrToken
+ Text.IPv6Addr.Types: DoubleColon :: IPv6AddrToken
+ Text.IPv6Addr.Types: EightBits :: Text -> IPv4AddrToken
+ Text.IPv6Addr.Types: IPv4Addr :: Text -> IPv6AddrToken
+ Text.IPv6Addr.Types: SixteenBits :: Text -> IPv6AddrToken
+ Text.IPv6Addr.Types: data IPv4AddrToken
+ Text.IPv6Addr.Types: data IPv6AddrToken
+ Text.IPv6Addr.Types: instance Eq IPv4AddrToken
+ Text.IPv6Addr.Types: instance Eq IPv6AddrToken
+ Text.IPv6Addr.Types: instance Show IPv4AddrToken
+ Text.IPv6Addr.Types: instance Show IPv6AddrToken
+ Text.IPv6Addr.Types: type IPv6Addr = Text

Files

IPv6Addr.cabal view
@@ -1,15 +1,15 @@ name:		IPv6Addr-version:	0.1+version:	0.2 license:	BSD3 license-file:	LICENSE-Copyright:      Copyright © 2011 - Michel Boucey+Copyright:      Copyright © 2011-2012 - Michel Boucey author:		Michel Boucey <michel.boucey@gmail.com> maintainer:	Michel Boucey <michel.boucey@gmail.com> Bug-Reports:    mailto:michel.boucey@gmail.com-synopsis:	Library to deal with the IPv6 address's text representation.-description:	Library to deal with IPv6 addresse's text representation with validation against RFC 4291 and canonization in conformation with RFC 5952.+synopsis:	Library to deal with IPv6 address text representation.+description:	Library to deal with IPv6 address text representation, canonization and manipulations. category:	Network, Text-stability:	stable+stability:	provisional build-type:	Simple cabal-version:	>= 1.6 source-repository head@@ -20,7 +20,10 @@   Build-Depends: base >= 3 && <= 5,                  bytestring >= 0.9,                  network-info >= 0.2,-                 text,-                 random+                 random,+                 text    exposed-modules: Text.IPv6Addr+                   Text.IPv6Addr.Internal+                   Text.IPv6Addr.Manip+                   Text.IPv6Addr.Types
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2011 - Michel Boucey+Copyright (c) 2011-2012 - Michel Boucey  All rights reserved. 
Text/IPv6Addr.hs view
@@ -1,32 +1,24 @@+-------------------------------------------------------------------------------- -- |  -- Module      :  Text.IPv6Addr--- Copyright   :  (c) Michel Boucey 2011+-- Copyright   :  (c) Michel Boucey 2011-2012 -- License     :  BSD-style -- Maintainer  :  michel.boucey@gmail.com -- Stability   :  provisional ----- Dealing with IPv6 address's text representation. Main features are validation against RFC 4291 and canonization in conformation with RFC 5952.+-- Dealing with IPv6 address text representation,+-- canonization and manipulations.+--+-------------------------------------------------------------------------------- -module Text.IPv6Addr(-    IPv6Addr,-    IPv6AddrToken(..),-    -- * Validating and canonizing an IPv6 Address-    isIPv6Addr,-    maybeIPv6Addr,-    maybeTokIPv6Addr,-    maybeExpIPv6Addr,-    getIPv6AddrOf,-    -- * Manipulating IPv6 address tokens-    -- ** To IPv6 Address token(s)-    maybeIPv6AddrTokens,-    macAddrToIPv6AddrTokens,-    getTokIPv6AddrOf,-    getTokMacAddrOf,-    sixteenBitsRand,-    ipv4AddrToIPv6AddrTokens,-    -- ** Back to Text-    ipv6TokensToText,-) where+module Text.IPv6Addr+    (+      IPv6Addr+    , maybeIPv6Addr+    , maybePureIPv6Addr+    , maybeFullIPv6Addr+    , getIPv6AddrOf+    ) where  import Control.Monad (replicateM) import Data.Char (intToDigit,isDigit,isHexDigit,toLower)@@ -35,384 +27,43 @@ import Data.Maybe (fromJust,isJust) import qualified Data.Text as T import Data.Text.Read (decimal)-import Network.Info import Numeric (showIntAtBase)-import System.Random (randomRIO) -type IPv6Addr = T.Text--data IPv6AddrToken-    = SixteenBits 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)--data IPv4AddrToken -    = Dot-    | EightBits T.Text deriving (Eq,Show)---- | Some useful tokens-tokdot = T.pack "."-tokcolon = T.pack ":"-tokdcolon = T.pack "::"-tok0 = T.pack "0"-tok1 = T.pack "1"-tokffff = T.pack "ffff"-tok64 = T.pack "64"-tokff9b = T.pack "ff9b"-tokfe80 = T.pack "fe80"-tok5efe = T.pack "5efe"-tok200 = T.pack "200"--tokenizeBy :: Char -> T.Text -> [T.Text]-tokenizeBy c = T.groupBy ((==) `on` (==c))------- Parsing embedded IPv4 address-----dot :: T.Text -> Maybe IPv4AddrToken-dot s = if s == tokdot then Just Dot else Nothing--eightBitsToken :: T.Text -> Maybe IPv4AddrToken-eightBitsToken t =-    case decimal t of-        Right p -> do let i = fst p-                      if i >= 0 && i <= 255 && snd p == T.empty-                          then Just (EightBits t) -                          else Nothing-        Left _ -> Nothing--ipv4Token :: T.Text -> Maybe IPv4AddrToken-ipv4Token t-    | isJust(dot t) = Just Dot-    | isJust(eightBitsToken t) = Just (EightBits t)-    | otherwise  = Nothing------- Parsing IPv6 Address------- | Returns an IPv4 address as an IPv6 address token.-ipv4Addr :: T.Text -> Maybe IPv6AddrToken-ipv4Addr t =-    do-        let r = map ipv4Token $ tokenizeBy '.' t-        if (Nothing `notElem` r) && (length r == 7)-            then Just (IPv4Addr t) else Nothing--colon :: T.Text -> Maybe IPv6AddrToken-colon t = if t == tokcolon then Just Colon else Nothing--doubleColon :: T.Text -> Maybe IPv6AddrToken-doubleColon t = if t == tokdcolon then Just DoubleColon else Nothing---- | Returns a SixteenBits token.-sixteenBits:: T.Text -> Maybe IPv6AddrToken-sixteenBits t =-    if T.length t < 5 then-        do-            -- "Leading zeros MUST be suppressed" (RFC 5952, 4.1)-            let t'= T.dropWhile (=='0') t-            if T.length t' < 5 && T.all isHexDigit t'-                then-                    if T.null t'-                        then Just AllZeros-                        -- Hexadecimal digits MUST be in lowercase (RFC 5952 4.3)-                        else Just $ SixteenBits $ T.toLower t'-                else Nothing-    else Nothing---- | Returns a random 'SixteenBits' token. E.g. sixteenBitsRand \"d\" may produce 'SixteenBits' \"d7b5\".-sixteenBitsRand :: String -> IO IPv6AddrToken-sixteenBitsRand s =-    if all isHexDigit s && l < 4-       then do-           a <- replicateM (4-l) hexRand-           return $ SixteenBits $ T.toLower $ T.pack $ s ++ a-       else return $ SixteenBits tok0--    where--        l = length s--        hexRand = do r <- randomRIO(0,15)-                     return $ intToDigit r+import Text.IPv6Addr.Internal+import Text.IPv6Addr.Types --- | Returns Just an 'IPv6Addr', or Nothing.+-- | Returns Just the text representation of a canonized+-- 'IPv6Addr' in conformation with RFC 5952, or Nothing. ----- > maybeIPv6Addr "D045::00Da:0fA9:0:0:230.34.110.80" == Just "d045:0:da:fa9::e622:6e50"+-- > maybeIPv6Addr "0:0::FFFF:192.0.2.128" == Just "::ffff:192.0.2.128" -- maybeIPv6Addr :: T.Text -> Maybe IPv6Addr-maybeIPv6Addr t = -     case maybeTokIPv6Addr t of-         Just a -> Just $ ipv6TokensToText a-         Nothing -> Nothing---- | Returns Just an expanded IPv6 address, or Nothing.-maybeExpIPv6Addr :: T.Text -> Maybe IPv6Addr-maybeExpIPv6Addr t = -     case maybeTokIPv6Addr t of-         Just a -> Just $ ipv6TokensToText $ fromDoubleColon a-         Nothing -> Nothing---- | Returns Just one of the valid 'IPv6AddrToken', or Nothing.-maybeIPv6AddrToken :: T.Text -> Maybe IPv6AddrToken-maybeIPv6AddrToken t-    | isJust t' = t'-    | isJust(colon t) = Just Colon-    | isJust(doubleColon t) = Just DoubleColon-    | isJust(ipv4Addr t) = Just (IPv4Addr t)-    | otherwise = Nothing--    where t' = sixteenBits t---- | Returns the corresponding 'Text' of an IPv6 address token.-ipv6TokenToText :: IPv6AddrToken -> T.Text-ipv6TokenToText (SixteenBits s) = s -ipv6TokenToText Colon = tokcolon-ipv6TokenToText DoubleColon = tokdcolon--- "A single 16-bit 0000 field MUST be represented as 0" (RFC 5952, 4.1)-ipv6TokenToText AllZeros = tok0-ipv6TokenToText (IPv4Addr a) = a---- | Given an arbitrary list of 'IPv6AddrToken', returns the corresponding 'Text'.-ipv6TokensToText :: [IPv6AddrToken] -> T.Text-ipv6TokensToText l = T.concat $ map ipv6TokenToText l---- | Returns True if a list of 'IPv6AddrToken' constitutes a valid IPv6 Address.-isIPv6Addr :: [IPv6AddrToken] -> Bool-isIPv6Addr [] = False-isIPv6Addr [DoubleColon] = True-isIPv6Addr [DoubleColon,SixteenBits tok1] = True-isIPv6Addr tks =-    diffNext tks && (do-        let cdctks = countDoubleColon tks-        let lentks = length tks-        let lasttk = last tks-        let lenconst = (lentks == 15 && cdctks == 0) || (lentks < 15 && cdctks == 1)-        firstValidToken tks &&-            (case countIPv4Addr tks of-                0 -> case lasttk of-                          SixteenBits _ -> lenconst-                          DoubleColon -> lenconst-                          AllZeros -> lenconst-                          otherwise -> False-                1 -> case lasttk of-                          IPv4Addr _ ->-                              (lentks == 13 && cdctks == 0) || (lentks < 12 && cdctks == 1)-                          otherwise -> False-                otherwise -> False))--            where--                diffNext [_] = True-                diffNext [a,a'] = a /= a'-                diffNext (a:as) = (a /= head as) && diffNext as--                firstValidToken l = -                    case head l of-                        SixteenBits _ -> True-                        DoubleColon -> True-                        AllZeros -> True-                        otherwise -> False--                countDoubleColon l = length $ elemIndices DoubleColon l--countIPv4Addr tks =-     foldr oneMoreIPv4Addr 0 tks-     where oneMoreIPv4Addr t c =-               case t of-                   IPv4Addr _ -> c + 1-                   otherwise -> c---- | Returns Just a list of 'IPv6AddrToken', or Nothing.-maybeIPv6AddrTokens :: T.Text -> Maybe [IPv6AddrToken]-maybeIPv6AddrTokens t = mapM maybeIPv6AddrToken (tokenizeBy ':' t)---- | This is the main function which returns Just the list of a tokenized IPv6 address's--- text representation validated against RFC 4291 and canonized (rewritten) in conformation--- with RFC 5952, or Nothing.-maybeTokIPv6Addr :: T.Text -> Maybe [IPv6AddrToken]-maybeTokIPv6Addr t =-    case maybeIPv6AddrTokens t of-        Nothing -> Nothing-        Just a ->-            if isIPv6Addr a-                then Just $ (toDoubleColon . ipv4AddrReplacement . fromDoubleColon) a-            else Nothing--        where--            ipv4AddrReplacement tks =-                if ipv4AddrRewrite tks then init tks ++ ipv4AddrToIPv6AddrTokens (last tks) else tks---- | 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,SixteenBits tokffff,Colon]-                || itks == [DoubleColon,SixteenBits tokffff,Colon,AllZeros,Colon]-                || itks == [SixteenBits tok64,Colon,SixteenBits tokff9b,DoubleColon]-                || [SixteenBits tok200,Colon,SixteenBits tok5efe,Colon] `isSuffixOf` itks-                || [AllZeros,Colon,SixteenBits tok5efe,Colon] `isSuffixOf` itks-                || [DoubleColon,SixteenBits tok5efe,Colon] `isSuffixOf` itks)-        otherwise -> False+maybeIPv6Addr t = maybeTokIPv6Addr t >>= ipv6TokensToText --- | Rewrites Just an embedded 'IPv4Addr' into the corresponding list of pure IPv6Addr tokens, or returns an empty list.+-- | Returns Just a pure 'IPv6Addr', or Nothing. ----- > ipv4AddrToIPv6AddrTokens (IPv4Addr "127.0.0.1") == [SixteenBits "7f0",Colon,SixteenBits "1"]+-- > maybePureIPv6Addr "::ffff:192.0.2.128" == Just "::ffff:c000:280" ---ipv4AddrToIPv6AddrTokens :: IPv6AddrToken -> [IPv6AddrToken]-ipv4AddrToIPv6AddrTokens t =-    case t of-        IPv4Addr a -> do-            let m = toHex a-            [fromJust $ sixteenBits ((!!) m 0 `T.append` addZero ((!!) m 1)),Colon,-             fromJust $ sixteenBits ((!!) m 2 `T.append` addZero ((!!) m 3))]-        otherwise -> []--        where--            toHex a = map (\x -> T.pack $ showIntAtBase 16 intToDigit (read (T.unpack x)::Int) "") $ T.split (=='.') a--            addZero d = if T.length d == 1 then T.pack "0" `T.append` d else d--fromDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]-fromDoubleColon tks = -    if DoubleColon `notElem` tks then tks-    else do-        let s = splitAt (fromJust $ elemIndex DoubleColon tks) tks-        let fsts = fst s-        let snds = if length(snd s) >= 1 then tail(snd s) else []-        let fste = if null fsts then [] else fsts ++ [Colon]-        let snde = if null snds then [] else Colon : snds-        fste ++ allZerosTokensReplacement(quantityOfAllZerosTokenToReplace tks) ++ snde--        where 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--              allZerosTokensReplacement x = intersperse Colon (replicate x AllZeros)--toDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]-toDoubleColon tks =-    zerosToDoubleColon tks (zerosRunToReplace $ zerosRunsList tks)--        where--            zerosToDoubleColon :: [IPv6AddrToken] -> (Int,Int) -> [IPv6AddrToken]-            -- 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 (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 $ takeWhile (/=(True,y)) x--                        longestLengthZerosRun x =-                            maximum $ map longest x--                              where longest t = case t of-                                                    (True,i) -> i-                                                    otherwise -> 0--            zerosRunsList x = map helper $ groupZerosRuns x--                where--                    helper h =-                        if head h == AllZeros-                        then (True,lh) else (False,lh)--                        where lh = length h--                    groupZerosRuns = group . filter (/= Colon)+maybePureIPv6Addr :: T.Text -> Maybe IPv6Addr+maybePureIPv6Addr t = maybeTokPureIPv6Addr t >>= ipv6TokensToText +-- | Returns Just a pure and expanded 'IPv6Addr', or Nothing. ----- Functions based upon Network.Info to get local MAC and IPv6 adresses.+-- > maybeFullIPv6Addr "::ffff:192.0.2.128" == Just "0000:0000:0000:0000:0000:ffff:c000:0280" --+maybeFullIPv6Addr :: T.Text -> Maybe IPv6Addr+maybeFullIPv6Addr t = do+    a <- maybeTokPureIPv6Addr t+    ipv6TokensToText $ expandTokens $ fromDoubleColon a --- | Given a MAC address, returns the corresponding 'IPv6AddrToken' list, or an empty list.+-- | Returns Just the canonized 'IPv6Addr' of the given network interface,+-- or Nothing. ----- > macAddrToIPv6AddrTokens "fa:1d:58:cc:95:16" == [SixteenBits "fa1d",Colon,SixteenBits "58cc",Colon,SixteenBits "9516"]+-- > getIPv6AddrOf "eth0" ---macAddrToIPv6AddrTokens :: T.Text -> [IPv6AddrToken]-macAddrToIPv6AddrTokens mac =-      if T.length mac == 17 then do-           let p = snd $ trans (T.split (==':') mac,[])-           if length p == 3 then-                  intersperse Colon $ map (fromJust . maybeIPv6AddrToken) p-               else []-           else []--       where trans ([],l) = ([],l)-             trans (l1,l2) = do let s = splitAt 2 l1-                                trans (snd s,l2 ++ [T.concat $ fst s]) --networkInterfacesIPv6AddrList :: IO [(String,IPv6)]-networkInterfacesIPv6AddrList = do-    n <- getNetworkInterfaces-    return $ map networkInterfacesIPv6Addr n --    where networkInterfacesIPv6Addr (NetworkInterface n _ a _) = (n,a)--networkInterfacesMacAddrList :: IO [(String,MAC)]-networkInterfacesMacAddrList = do-    n <- getNetworkInterfaces-    return $ map networkInterfacesMac n --    where networkInterfacesMac (NetworkInterface n _ _ m) = (n,m)---- | Given a valid name of a local network interface, e.g. getIPv6AddrOf \"eth0\", return Just the interface's IPv6 address. getIPv6AddrOf :: String -> IO (Maybe IPv6Addr) getIPv6AddrOf s = do      l <- networkInterfacesIPv6AddrList      case lookup s l of          Just a -> return $ maybeIPv6Addr $ T.pack $ show a          Nothing -> return Nothing---- | Given a valid name of a local network interface, returns Just the list of tokens of the interface's IPv6 address.-getTokIPv6AddrOf :: String -> IO (Maybe [IPv6AddrToken])-getTokIPv6AddrOf s = do-     l <- networkInterfacesIPv6AddrList-     case lookup s l of-         Just a -> return $ maybeTokIPv6Addr $ T.pack $ show a-         Nothing -> return Nothing---- | Given the valid name of a local network interface, returns the corresponding list of 'IPv6AddrToken' of the interface's MAC Address.-getTokMacAddrOf :: String -> IO (Maybe [IPv6AddrToken])-getTokMacAddrOf s = do-    l <-networkInterfacesMacAddrList-    case lookup s l of-        Just a -> return $ Just $ macAddrToIPv6AddrTokens $ T.pack $ show a-        Nothing -> return Nothing
+ Text/IPv6Addr/Internal.hs view
@@ -0,0 +1,311 @@+--------------------------------------------------------------------------------+-- | +-- Module      :  Text.IPv6Addr+-- Copyright   :  (c) Michel Boucey 2011-2012+-- License     :  BSD-style+-- Maintainer  :  michel.boucey@gmail.com+-- Stability   :  provisional+--+-- Dealing with IPv6 address text representation,+-- canonization and manipulations.+--+--------------------------------------------------------------------------------++module Text.IPv6Addr.Internal+    (+      colon+    , doubleColon+    , sixteenBits+    , ipv4Addr+    , expandTokens+    , maybeIPv6AddrToken+    , maybeIPv6AddrTokens+    , ipv6TokensToText+    , isIPv6Addr+    , maybeTokIPv6Addr+    , maybeTokPureIPv6Addr+    , ipv4AddrToIPv6AddrTokens+    , fromDoubleColon+    , toDoubleColon+    , networkInterfacesIPv6AddrList+    ) where++import Control.Monad (replicateM)+import Data.Char (isDigit,isHexDigit,toLower)+import Data.Function (on)+import Data.List (group,isSuffixOf,elemIndex,elemIndices,intersperse)+import Numeric (showHex)+import qualified Data.Text as T+import Data.Text.Read (decimal)+import Data.Maybe (fromJust,isJust)+import Network.Info++import Text.IPv6Addr.Types++tokdot = T.pack "."+tokcolon = T.pack ":"+tokdcolon = T.pack "::"+tok0 = T.pack "0"+tok4x0 = T.pack "0000"+tok1 = T.pack "1"+tokffff = T.pack "ffff"+tok64 = T.pack "64"+tokff9b = T.pack "ff9b"+tokfe80 = T.pack "fe80"+tok5efe = T.pack "5efe"+tok200 = T.pack "200"++tokenizeBy :: Char -> T.Text -> [T.Text]+tokenizeBy c = T.groupBy ((==) `on` (== c))++--+-- Validation of IPv6 adress tokens+--++dot :: T.Text -> Maybe IPv4AddrToken+dot s = if s == tokdot then Just Dot else Nothing++eightBitsToken :: T.Text -> Maybe IPv4AddrToken+eightBitsToken t =+    case decimal t of+        Right p -> do let i = fst p+                      if i >= 0 && i <= 255 && snd p == T.empty+                         then Just (EightBits t) else Nothing+        Left _ -> Nothing++ipv4Token :: T.Text -> Maybe IPv4AddrToken+ipv4Token t+    | isJust(dot t) = Just Dot+    | isJust(eightBitsToken t) = Just (EightBits t)+    | otherwise = Nothing++ipv4Addr :: T.Text -> Maybe IPv6AddrToken+ipv4Addr t = do+    let r = map ipv4Token $ tokenizeBy '.' t+    if (Nothing `notElem` r) && (length r == 7)+       then Just (IPv4Addr t) else Nothing++colon :: T.Text -> Maybe IPv6AddrToken+colon t = if t == tokcolon+             then Just Colon+             else Nothing++doubleColon :: T.Text -> Maybe IPv6AddrToken+doubleColon t = if t == tokdcolon+                   then Just DoubleColon+                   else Nothing++sixteenBits:: T.Text -> Maybe IPv6AddrToken+sixteenBits t =+    if T.length t < 5+       then do+            -- "Leading zeros MUST be suppressed" (RFC 5952, 4.1)+            let t'= T.dropWhile (=='0') t+            if T.length t' < 5 && T.all isHexDigit t'+               then if T.null t'+                       then Just AllZeros+                       -- Hexadecimal digits MUST be in lowercase (RFC 5952 4.3)+                       else Just $ SixteenBits $ T.toLower t'+               else Nothing+       else Nothing++expandTokens :: [IPv6AddrToken] -> [IPv6AddrToken]+expandTokens =+    map expTok+  where expTok AllZeros = SixteenBits tok4x0+        expTok (SixteenBits s) = do+            let ls = T.length s+            if ls < 4+               then SixteenBits (T.replicate (4 - ls) tok0 `T.append` s)+               else SixteenBits s+        expTok t = t++-- | Returns Just one of the valid 'IPv6AddrToken', or Nothing.+maybeIPv6AddrToken :: T.Text -> Maybe IPv6AddrToken+maybeIPv6AddrToken t+    | isJust t' = t'+    | isJust(colon t) = Just Colon+    | isJust(doubleColon t) = Just DoubleColon+    | isJust(ipv4Addr t) = Just (IPv4Addr t)+    | otherwise = Nothing+  where t' = sixteenBits t++-- | Returns the corresponding 'Text' of an IPv6 address token.+ipv6TokenToText :: IPv6AddrToken -> T.Text+ipv6TokenToText (SixteenBits s) = s +ipv6TokenToText Colon = tokcolon+ipv6TokenToText DoubleColon = tokdcolon+-- "A single 16-bit 0000 field MUST be represented as 0" (RFC 5952, 4.1)+ipv6TokenToText AllZeros = tok0+ipv6TokenToText (IPv4Addr a) = a++-- | Given an arbitrary list of 'IPv6AddrToken', returns the corresponding 'Text'.+ipv6TokensToText :: [IPv6AddrToken] -> Maybe T.Text+ipv6TokensToText l = Just $ T.concat $ map ipv6TokenToText l++-- | Returns True if a list of 'IPv6AddrToken' constitutes a valid IPv6 Address.+isIPv6Addr :: [IPv6AddrToken] -> Bool+isIPv6Addr [] = False+isIPv6Addr [DoubleColon] = True+isIPv6Addr [DoubleColon,SixteenBits tok1] = True+isIPv6Addr tks =+    diffNext tks && (do+        let cdctks = countDoubleColon tks+        let lentks = length tks+        let lasttk = last tks+        let lenconst = (lentks == 15 && cdctks == 0) || (lentks < 15 && cdctks == 1)+        firstValidToken tks &&+            (case countIPv4Addr tks of+                0 -> case lasttk of+                          SixteenBits _ -> lenconst+                          DoubleColon -> lenconst+                          AllZeros -> lenconst+                          otherwise -> False+                1 -> case lasttk of+                          IPv4Addr _ ->+                              (lentks == 13 && cdctks == 0) || (lentks < 12 && cdctks == 1)+                          otherwise -> False+                otherwise -> False))+          where diffNext [_] = True+                diffNext [a,a'] = a /= a'+                diffNext (a:as) = (a /= head as) && diffNext as+                firstValidToken l = +                    case head l of+                        SixteenBits _ -> True+                        DoubleColon -> True+                        AllZeros -> True+                        otherwise -> False+                countDoubleColon l = length $ elemIndices DoubleColon l++countIPv4Addr =+     foldr oneMoreIPv4Addr 0+   where oneMoreIPv4Addr t c = case t of+                                   IPv4Addr _ -> c + 1+                                   otherwise -> c++-- | Returns Just a list of 'IPv6AddrToken', or Nothing.+maybeIPv6AddrTokens :: T.Text -> Maybe [IPv6AddrToken]+maybeIPv6AddrTokens t = mapM maybeIPv6AddrToken $ tokenizeBy ':' t++-- | This is the main function which returns Just the list of a tokenized IPv6+-- address's text representation validated against RFC 4291 and canonized+-- in conformation with RFC 5952, or Nothing.+maybeTokIPv6Addr :: T.Text -> Maybe [IPv6AddrToken]+maybeTokIPv6Addr t = +    do ltks <- maybeIPv6AddrTokens t+       if isIPv6Addr ltks+          -- then Just $ (toDoubleColon . ipv4AddrReplacement . fromDoubleColon) ltks+          then Just $ (ipv4AddrReplacement . toDoubleColon . fromDoubleColon) ltks+          else 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+       if isIPv6Addr ltks+          then Just $ (toDoubleColon . ipv4AddrReplacement . fromDoubleColon) ltks+          else Nothing+     where ipv4AddrReplacement ltks' =+               init ltks' ++ ipv4AddrToIPv6AddrTokens (last ltks')++-- | 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,SixteenBits tokffff,Colon]+                 || itks == [DoubleColon,SixteenBits tokffff,Colon,AllZeros,Colon]+                 || itks == [SixteenBits tok64,Colon,SixteenBits tokff9b,DoubleColon]+                 || [SixteenBits tok200,Colon,SixteenBits tok5efe,Colon] `isSuffixOf` itks+                 || [AllZeros,Colon,SixteenBits tok5efe,Colon] `isSuffixOf` itks+                 || [DoubleColon,SixteenBits tok5efe,Colon] `isSuffixOf` itks)+        otherwise -> False++-- | Rewrites Just 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+            [fromJust $ sixteenBits ((!!) m 0 `T.append` addZero ((!!) m 1))+             ,Colon+             ,fromJust $ sixteenBits ((!!) m 2 `T.append` 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 tok0 `T.append` d else d++fromDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]+fromDoubleColon tks = +    if DoubleColon `notElem` tks then tks+    else do+        let s = splitAt (fromJust $ elemIndex DoubleColon tks) tks+        let fsts = fst s+        let snds = if length(snd s) >= 1 then tail(snd s) else []+        let fste = if null fsts then [] else fsts ++ [Colon]+        let snde = if null snds then [] else Colon : snds+        fste ++ allZerosTokensReplacement(quantityOfAllZerosTokenToReplace tks) ++ snde+      where 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+            allZerosTokensReplacement x = intersperse Colon (replicate x AllZeros)++toDoubleColon :: [IPv6AddrToken] -> [IPv6AddrToken]+toDoubleColon tks =+    zerosToDoubleColon tks (zerosRunToReplace $ zerosRunsList tks)+  where+    zerosToDoubleColon :: [IPv6AddrToken] -> (Int,Int) -> [IPv6AddrToken]+    -- 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 (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 $ takeWhile (/=(True,y)) x+            longestLengthZerosRun x =+                maximum $ map longest x+              where longest t = case t of+                                    (True,i) -> i+                                    otherwise -> 0+    zerosRunsList x =+        map helper $ groupZerosRuns x+      where+        helper h =+            (head h == AllZeros, lh)+          where lh = length h+        groupZerosRuns = group . filter (/= Colon)++networkInterfacesIPv6AddrList :: IO [(String,IPv6)]+networkInterfacesIPv6AddrList = do+    n <- getNetworkInterfaces+    return $ map networkInterfacesIPv6Addr n+  where networkInterfacesIPv6Addr (NetworkInterface n _ a _) = (n,a)
+ Text/IPv6Addr/Manip.hs view
@@ -0,0 +1,103 @@+--------------------------------------------------------------------------------+-- |+-- Module      :  Text.IPv6Addr+-- Copyright   :  (c) Michel Boucey 2011-212+-- License     :  BSD-style+-- Maintainer  :  michel.boucey@gmail.com+-- Stability   :  provisional+--+-- Dealing with IPv6 address's text representation,+-- canonization and manipulations.+--+--------------------------------------------------------------------------------++module Text.IPv6Addr.Manip+    (+      module Text.IPv6Addr.Internal+    , sixteenBitsArbToken+    , macAddrToIPv6AddrTokens+    , getTokIPv6AddrOf+    , getTokMacAddrOf+    ) where++import Control.Monad (replicateM)+import Data.Char (intToDigit,isHexDigit)+import Data.List (intersperse)+import Data.Maybe (catMaybes,fromJust)+import qualified Data.Text as T+import System.Random (randomRIO)++import Network.Info++import Text.IPv6Addr+import Text.IPv6Addr.Internal+import Text.IPv6Addr.Types++-- | Returns an arbitrary 'SixteenBits' token based on a mask \"____\", each+-- underscore being replaced by a random hexadecimal digit.+--+-- > sixteenBitsArbToken "_f__" == Just (SixteenBits "bfd4")+-- +sixteenBitsArbToken :: String -> IO (Maybe IPv6AddrToken)+sixteenBitsArbToken m = do+    cs <- mapM getHex m+    return $ sixteenBits $ T.pack cs+  where getHex c =+            case c of+                '_' -> hexRand+                otherwise -> return c+          where hexRand = do+                    r <- randomRIO(0,15)+                    return $ intToDigit r++ipv6AddrRand :: IO (Maybe IPv6Addr)+ipv6AddrRand = do+    l <- replicateM 8 (sixteenBitsArbToken "____")+    return $ ipv6TokensToText $ intersperse Colon $ catMaybes l++-- | Given a MAC address, returns the corresponding 'IPv6AddrToken' list, or an empty list.+--+-- > macAddrToIPv6AddrTokens "fa:1d:58:cc:95:16" == [SixteenBits "fa1d",Colon,SixteenBits "58cc",Colon,SixteenBits "9516"]+--+macAddrToIPv6AddrTokens :: T.Text -> [IPv6AddrToken]+macAddrToIPv6AddrTokens mac =+    if T.length mac == 17+    then do+        let p = snd $ trans (T.split (==':') mac,[])+        if length p == 3+           then intersperse Colon $ map (fromJust . maybeIPv6AddrToken) p+           else []+    else []+  where+    trans ([],l) = ([],l)+    trans (l1,l2) = do+        let s = splitAt 2 l1+        trans (snd s,l2 ++ [T.concat $ fst s]) ++--+-- Functions based upon Network.Info to get local MAC and IPv6 adresses.+--++networkInterfacesMacAddrList :: IO [(String,MAC)]+networkInterfacesMacAddrList = do+    n <- getNetworkInterfaces+    return $ map networkInterfacesMac n +  where networkInterfacesMac (NetworkInterface n _ _ m) = (n,m)++-- | Given a valid name of a local network interface, returns Just the list of+-- tokens of the interface's IPv6 address.+getTokIPv6AddrOf :: String -> IO (Maybe [IPv6AddrToken])+getTokIPv6AddrOf s = do+     l <- networkInterfacesIPv6AddrList+     case lookup s l of+         Just a -> return $ maybeTokIPv6Addr $ T.pack $ show a+         Nothing -> return Nothing++-- | Given the valid name of a local network interface,+-- returns the corresponding list of 'IPv6AddrToken' of the interface's MAC Address.+getTokMacAddrOf :: String -> IO (Maybe [IPv6AddrToken])+getTokMacAddrOf s = do+    l <- networkInterfacesMacAddrList+    case lookup s l of+        Just a -> return $ Just $ macAddrToIPv6AddrTokens $ T.pack $ show a+        Nothing -> return Nothing
+ Text/IPv6Addr/Types.hs view
@@ -0,0 +1,30 @@+--------------------------------------------------------------------------------+-- |+-- Module      :  Text.IPv6Addr+-- Copyright   :  (c) Michel Boucey 2011+-- License     :  BSD-style+-- Maintainer  :  michel.boucey@gmail.com+-- Stability   :  provisional+--+-- Dealing with IPv6 address's text representation,+-- canonization and manipulations.+--+--------------------------------------------------------------------------------++module Text.IPv6Addr.Types where++import qualified Data.Text as T++type IPv6Addr = T.Text++data IPv6AddrToken+    = SixteenBits 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)++data IPv4AddrToken+    = Dot+    | EightBits T.Text deriving (Eq,Show)