{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
module HaskellWorks.Data.Network.Ip.Ipv6
( IpAddress(..)
, IpNetMask(..)
, IpBlock(..)
, fromV4
, parseIpBlock
, masksIp
, showIpAddress
, showsIpAddress
, tshowIpAddress
, tshowIpBlock
, firstIpAddress
, lastIpAddress
, rangeToBlocks
, rangeToBlocksDL
, blockToRange
, isCanonical
, splitIpRange
) where
import Control.Applicative
import Control.Monad
import Data.Bifunctor
import Data.Maybe
import Data.Word
import GHC.Generics
import HaskellWorks.Data.Network.Ip.Range
import HaskellWorks.Data.Network.Ip.SafeEnum
import HaskellWorks.Data.Network.Ip.Validity
import Prelude hiding (words)
import Text.Read
import qualified Data.Bits as B
import qualified Data.IP as D
import qualified Data.Text as T
import qualified HaskellWorks.Data.Network.Ip.Internal as I
import qualified HaskellWorks.Data.Network.Ip.Ipv4 as V4
import qualified HaskellWorks.Data.Network.Ip.Word128 as W
newtype IpAddress = IpAddress W.Word128 deriving (Enum, Eq, Ord, Generic, SafeEnum)
instance Show IpAddress where
showsPrec _ (IpAddress w) = shows (D.fromHostAddress6 w)
instance Read IpAddress where
readsPrec :: Int -> String -> [(IpAddress, String)]
readsPrec _ s =
case readMaybe s :: Maybe D.IPv6 of
Just ip -> [(IpAddress (D.toHostAddress6 ip), "")]
Nothing -> []
newtype IpNetMask = IpNetMask
{ word :: Word8
} deriving (Enum, Eq, Ord, Show, Generic)
instance Read IpNetMask where
readsPrec _ s =
case IpNetMask <$> m of
Just maskv6 -> [(maskv6, "")]
Nothing -> []
where
m = mfilter (\a -> a >= 0 && a <= 128) (readMaybe s)
data IpBlock v = IpBlock
{ base :: !IpAddress
, mask :: !IpNetMask
} deriving (Eq, Ord, Generic)
instance Read (IpBlock Unaligned) where
readsPrec _ s =
case T.unpack <$> T.split (== '/') (T.pack s) of
[addr, msk] ->
case readMaybe addr :: Maybe IpAddress of
Just ipv6 ->
case readMaybe msk of
Just mskv6 ->
let i6b = IpBlock ipv6 mskv6 in
[(i6b, "") | isCanonical i6b]
Nothing -> []
Nothing -> []
[addr] ->
case readMaybe addr :: Maybe IpAddress of
Just ipv6 -> let i6b = IpBlock ipv6 (IpNetMask 128) in [(i6b, "") | isCanonical i6b]
Nothing -> []
_ -> []
instance Show (IpBlock v) where
showsPrec _ (IpBlock b (IpNetMask m)) = shows b . ('/':) . shows m
parseIpBlock :: T.Text -> Either T.Text (IpBlock Unaligned)
parseIpBlock t =
case T.unpack <$> T.split (== '/') t of
[addr, msk] ->
case readMaybe addr :: Maybe IpAddress of
Just ipv6 ->
case readMaybe msk of
Just mskv6 -> Right $ IpBlock ipv6 mskv6
Nothing -> Left "cannot read mask"
Nothing -> Left "cannot read addr"
_ -> Left "invalid input string"
showsIpAddress :: IpAddress -> String -> String
showsIpAddress (IpAddress w) = shows (D.fromHostAddress6 w)
showIpAddress :: IpAddress -> String
showIpAddress ipAddress = showsIpAddress ipAddress ""
tshowIpAddress :: IpAddress -> T.Text
tshowIpAddress = T.pack . showIpAddress
showsIpBlock :: IpBlock v -> String -> String
showsIpBlock (IpBlock b (IpNetMask m)) = shows b . ('/':) . shows m
showIpBlock :: IpBlock v -> String
showIpBlock ipBlock = showsIpBlock ipBlock ""
tshowIpBlock :: IpBlock v -> T.Text
tshowIpBlock = T.pack . showIpBlock
masksIp :: Word8 -> [Word32]
masksIp m =
let e = 0xFFFFFFFF :: Word32
maskValue bits = e `B.shiftR` (32 - bits) in
if m < 32 then
[maskValue (32 - fromIntegral m), e, e, e]
else if m < 64 then
[0, maskValue (64 - fromIntegral m), e, e]
else if m < 96 then
[0, 0, maskValue (96 - fromIntegral m), e]
else if m < 128 then
[0, 0, 0, maskValue (128 - fromIntegral m)]
else
[0, 0, 0, 0]
isCanonical :: IpBlock v -> Bool
isCanonical (IpBlock (IpAddress w) (IpNetMask m)) =
let lt = masksIp m
ipv6 = I.word32x4ToWords w in
ipv6 == zipWith (B..&.) ipv6 (zipWith B.xor ipv6 lt)
fromV4 :: V4.IpBlock Canonical -> IpBlock v
fromV4 (V4.IpBlock b m) =
IpBlock (IpAddress (0, 0, 0xFFFF, V4.word b)) (IpNetMask (96 + V4.word8 m))
firstIpAddress :: IpBlock Canonical -> IpAddress
firstIpAddress (IpBlock b _) = b
lastIpAddress :: IpBlock Canonical -> IpAddress
lastIpAddress (IpBlock (IpAddress b) (IpNetMask m)) = IpAddress (b + fromIntegral (I.blockSize128 m) - 1)
splitIpRange :: Range IpAddress -> (IpBlock Canonical, Maybe (Range IpAddress))
splitIpRange (Range (IpAddress a) (IpAddress z)) = (block, remainder)
where bpOuter = width - B.countLeadingZeros (z + 1 - a) - 1
bpInner = B.countTrailingZeros ((maxBound `B.shiftL` fromIntegral bpOuter) B..|. a)
block = IpBlock (IpAddress a) (IpNetMask (fromIntegral (width - bpInner)))
hostMask = B.complement (maxBound `B.shiftL` fromIntegral bpInner)
remainder = if a + hostMask >= z
then Nothing
else Just (Range (IpAddress (a + hostMask + 1)) (IpAddress z))
width = B.finiteBitSize a
rangeToBlocksDL :: Range IpAddress -> [IpBlock Canonical] -> [IpBlock Canonical]
rangeToBlocksDL r = do
let (b, remainder) = splitIpRange r
case remainder of
Just rmd -> (b:) . rangeToBlocksDL rmd
Nothing -> (b:)
rangeToBlocks :: Range IpAddress -> [IpBlock Canonical]
rangeToBlocks r = rangeToBlocksDL r []
blockToRange :: IpBlock Canonical -> Range IpAddress
blockToRange b = uncurry Range $ bimap firstIpAddress lastIpAddress (b, b)