packages feed

IPv6Addr (empty) → 0.1

raw patch · 4 files changed

+476/−0 lines, 4 filesdep +basedep +bytestringdep +network-infosetup-changed

Dependencies added: base, bytestring, network-info, random, text

Files

+ IPv6Addr.cabal view
@@ -0,0 +1,26 @@+name:		IPv6Addr+version:	0.1+license:	BSD3+license-file:	LICENSE+Copyright:      Copyright © 2011 - 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.+category:	Network, Text+stability:	stable+build-type:	Simple+cabal-version:	>= 1.6+source-repository head+  type: git+  location: https://github.com/MichelBoucey/IPv6Addr++Library+  Build-Depends: base >= 3 && <= 5,+                 bytestring >= 0.9,+                 network-info >= 0.2,+                 text,+                 random++  exposed-modules: Text.IPv6Addr
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2011 - Michel Boucey++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Text/IPv6Addr.hs view
@@ -0,0 +1,418 @@+-- | +-- 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. Main features are validation against RFC 4291 and canonization in conformation with RFC 5952.++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++import Control.Monad (replicateM)+import Data.Char (intToDigit,isDigit,isHexDigit,toLower)+import Data.Function (on)+import Data.List (group,isSuffixOf,elemIndex,elemIndices,intersperse)+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++-- | Returns Just an 'IPv6Addr', or Nothing.+--+-- > maybeIPv6Addr "D045::00Da:0fA9:0:0:230.34.110.80" == Just "d045:0:da:fa9::e622:6e50"+--+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++-- | Rewrites Just an embedded 'IPv4Addr' into the corresponding list of pure IPv6Addr tokens, or returns an empty list.+--+-- > 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))]+        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)++--+-- Functions based upon Network.Info to get local MAC and IPv6 adresses.+--++-- | 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]) ++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