ip6addr-0.3.1: src/IPv6Addr.hs
module IPv6Addr where
import Data.Char (intToDigit,isDigit,isHexDigit,toLower)
import Data.List (group,groupBy,intercalate,intersperse,elemIndex)
import Data.Function (on)
import Data.Maybe
import Numeric
type IPv6Addr = String
data IPv4AddrToken = Dot | EightBits String deriving (Eq)
data IPv6AddrToken = SixteenBits String | AllZeros | Colon | DoubleColon
| IPv4Addr String deriving (Eq)
tokenizedBy :: String -> Char -> [String]
tokenizedBy s c = groupBy ((==) `on` ( == c)) s
-- Parsing embedded IPv4 address
dot :: String -> Maybe IPv4AddrToken
dot s = if s == "." then Just Dot else Nothing
eightBitsToken :: String -> Maybe IPv4AddrToken
eightBitsToken s =
let t = read s::Int
in
if not(null s) && all isDigit s && t >= 0 && t <= 255
then Just (EightBits s) else Nothing
ipv4Token :: String -> Maybe IPv4AddrToken
ipv4Token s
| isJust(dot s) = Just Dot
| isJust(eightBitsToken s) = Just (EightBits s)
| otherwise = Nothing
ipv4Addr :: String -> Maybe IPv6AddrToken
ipv4Addr s =
do
let r = map ipv4Token (s `tokenizedBy` '.')
if (Nothing `notElem` r) && (length r == 7)
then Just (IPv4Addr s) else Nothing
-- Parsing IPv6 Address
colon :: String -> Maybe IPv6AddrToken
colon s = if s == ":" then Just Colon else Nothing
doubleColon :: String -> Maybe IPv6AddrToken
doubleColon s = if s == "::" then Just DoubleColon else Nothing
sixteenBits:: String -> Maybe IPv6AddrToken
sixteenBits s =
if length s < 5 then
do
-- "Leading zeros MUST be suppressed" (RFC 5952, 4.1)
let s'= dropWhile (=='0') s
if length s' < 5 && all isHexDigit s'
then case s' of
"" -> Just AllZeros
-- Hexadecimal digits MUST be in lowercase (RFC 5952 4.3)
otherwise -> Just (SixteenBits (map toLower s'))
else Nothing
else Nothing
ipv6AddrToken :: String -> Maybe IPv6AddrToken
ipv6AddrToken s
| isJust s' = s'
| isJust(colon s) = Just Colon
| isJust(doubleColon s) = Just DoubleColon
| isJust(ipv4Addr s) = Just (IPv4Addr s)
| otherwise = Nothing
where s' = sixteenBits s
ipv6AddrTokenToString :: IPv6AddrToken -> String
ipv6AddrTokenToString (SixteenBits t) = t
ipv6AddrTokenToString Colon = ":"
ipv6AddrTokenToString DoubleColon = "::"
-- "A single 16-bit 0000 field MUST be represented as 0" (RFC 5952, 4.1)
ipv6AddrTokenToString AllZeros = "0"
ipv6AddrTokenToString (IPv4Addr i) = i
ipv6AddrToString :: [Maybe IPv6AddrToken] -> IPv6Addr
ipv6AddrToString t = concatMap ipv6AddrTokenToString (catMaybes t)
countIPv4Addr :: [Maybe IPv6AddrToken] -> Int
countIPv4Addr ts =
foldr oneMoreIPv4Token 0 ts
where
oneMoreIPv4Token t c =
case t of
Just (IPv4Addr _) -> c + 1
otherwise -> c
countDoubleColon :: [Maybe IPv6AddrToken] -> Int
countDoubleColon ts =
foldr oneMoreDoubleColon 0 ts
where
oneMoreDoubleColon t c =
case t of
Just DoubleColon -> c + 1
otherwise -> c
firstValidToken :: [Maybe IPv6AddrToken] -> Bool
firstValidToken ts =
case head ts of
Just (SixteenBits _) -> True
Just DoubleColon -> True
Just AllZeros -> True
otherwise -> False
ipv6AddrConstraints :: [Maybe IPv6AddrToken] -> Bool
ipv6AddrConstraints [] = False
ipv6AddrConstraints [Just DoubleColon] = True
ipv6AddrConstraints [Just DoubleColon,Just (SixteenBits "1")] = True
ipv6AddrConstraints ts =
do
let cdcts = countDoubleColon ts
let lents = length ts
let lastt = last ts
let lenconst = ( (lents == 15 && cdcts <= 0) || (lents < 15 && cdcts == 1) )
((firstValidToken ts && all isJust ts) &&
(case countIPv4Addr ts of
0 -> case lastt of
Just (SixteenBits _) -> lenconst
Just DoubleColon -> lenconst
Just AllZeros -> lenconst
otherwise -> False
1 -> case lastt of
Just (IPv4Addr _) -> (lents == 13 && cdcts <= 0) || (lents < 13 && cdcts == 1)
otherwise -> False
otherwise -> False))
maybeIPv6Addr :: String -> Maybe IPv6Addr
maybeIPv6Addr "::" = Just "::"
maybeIPv6Addr "::1" = Just "::1"
maybeIPv6Addr s =
do
let ts = map ipv6AddrToken (s `tokenizedBy` ':')
if ipv6AddrConstraints ts
then
(if Just DoubleColon `notElem` ts
then Just $ ipv6AddrToString (ipv4AddrToHex $ replaceTheLongestZerosRun ts)
else Just $ ipv6AddrToString (ipv4AddrToHex $ replaceTheLongestZerosRun (expendDoubleColon ts)))
else Nothing
-- The 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"
ipv4AddrToHex :: [Maybe IPv6AddrToken] -> [Maybe IPv6AddrToken]
ipv4AddrToHex [Just DoubleColon,Just (IPv4Addr a)] =
[Just DoubleColon,Just Colon,Just (IPv4Addr a)]
ipv4AddrToHex [Just DoubleColon,Just (SixteenBits "ffff"),Just Colon,Just (IPv4Addr a)] =
[Just DoubleColon,Just (SixteenBits "ffff"),Just Colon,Just (IPv4Addr a)]
ipv4AddrToHex [Just DoubleColon,Just (SixteenBits "ffff"),Just Colon,Just AllZeros,Just Colon,Just (IPv4Addr a)] =
[Just DoubleColon,Just (SixteenBits "ffff"),Just Colon,Just AllZeros,Just Colon,Just (IPv4Addr a)]
ipv4AddrToHex [Just (SixteenBits "64"),Just Colon,Just (SixteenBits "ff9b"),Just DoubleColon,Just (IPv4Addr a)] =
[Just (SixteenBits "64"),Just Colon,Just (SixteenBits "ff9b"),Just DoubleColon,Just (IPv4Addr a)]
ipv4AddrToHex [Just (SixteenBits "fe80"),Just DoubleColon,Just (SixteenBits "5efe"),Just Colon,Just (IPv4Addr a)] =
[Just (SixteenBits "fe80"),Just DoubleColon,Just (SixteenBits "5efe"),Just Colon,Just (IPv4Addr a)]
ipv4AddrToHex ts =
do
let t = last ts
case t of
Just (IPv4Addr a) -> do
let m = map (\x -> showIntAtBase 16 intToDigit (read x::Int) "")
$ filter (/= ".") (a `tokenizedBy` '.')
init ts ++ [Just (SixteenBits ((!!) m 0 ++ addZero((!!) m 1))),Just Colon,Just (SixteenBits ((!!) m 2 ++ addZero((!!) m 3)))]
where
addZero s = if length s < 2 then '0':s else s
otherwise -> ts
expendDoubleColon :: [Maybe IPv6AddrToken] -> [Maybe IPv6AddrToken]
expendDoubleColon mit = do
let s = splitAt (fromJust $ elemIndex (Just DoubleColon) mit) mit
let fsts = fst s
let snds = if length(snd s) >= 1 then tail(snd s) else []
let fste = if null fsts then [] else fsts ++ [Just Colon]
let snde = if null snds then [] else Just Colon : snds
fste ++ allZerosTokensReplacement(quantityOfAllZerosTokenToReplace mit) ++ snde
where
quantityOfAllZerosTokenToReplace x =
8 - foldl (\c x -> if (x /= Just DoubleColon) && (x /= Just Colon)
then c+1 else c) 0 x
allZerosTokensReplacement x =
intersperse (Just Colon) (replicate x (Just AllZeros))
-- "the longest run of consecutive 16-bit 0 fields MUST be shortened" (RFC 5952, 4.2.3)
-- All the stuff to deal with replacement of the longest zeros run by a double colon
longestLengthZerosRun :: (Num a, Ord a) => [(Bool, a)] -> a
longestLengthZerosRun x =
maximum $ map longest x
where
longest t =
case t of
(True,i) -> i
otherwise -> 0
replaceTheLongestZerosRun :: [Maybe IPv6AddrToken] -> [Maybe IPv6AddrToken]
replaceTheLongestZerosRun s' =
zerosToDoubleColon s' (zerosRunToReplace $ zerosRunsList s')
where
zerosRunToReplace t =
let l = longestLengthZerosRun t
in (firstLongestZerosRunIndex t l,l)
firstLongestZerosRunIndex :: (Num b) => [(Bool, b)] -> b -> b
firstLongestZerosRunIndex x y =
sum . snd . unzip $ takeWhile (/=(True,y)) x
zerosRunsList :: [Maybe IPv6AddrToken] -> [(Bool, Int)]
zerosRunsList x =
map helper $ groupZerosRuns x
where
helper h =
if head h == Just AllZeros
then (True,lh) else (False,lh)
where lh = length h
groupZerosRuns =
group . filter (/= Just Colon)
zerosToDoubleColon :: [Maybe IPv6AddrToken] -> (Int,Int) -> [Maybe 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 (/= Just Colon) ls
in intersperse (Just Colon) (take i ls')
++ [Just DoubleColon]
++ intersperse (Just Colon) (drop (i+l) ls')