module Net.IPv6
(
IPv6(..)
, ipv6
, fromOctets
, fromWord16s
, fromWord32s
, fromTupleWord16s
, fromTupleWord32s
, toWord16s
, toWord32s
, any
, loopback
, encode
, decode
, parser
, print
) where
import Prelude hiding (any, print)
import Data.Bits
import Data.List (intercalate, group)
import Data.Word
import Data.Char (chr)
import Control.Applicative
import Data.Text (Text)
import Text.Read (Read(..),Lexeme(Ident),lexP,parens)
import Text.ParserCombinators.ReadPrec (prec,step)
import qualified Data.Text as Text
import qualified Data.Text.IO as TIO
import qualified Data.Attoparsec.Text as Atto
import qualified Data.Aeson as Aeson
import qualified Data.Attoparsec.Text as AT
import Numeric (showHex)
data IPv6 = IPv6
{ ipv6A :: !Word64
, ipv6B :: !Word64
} deriving (Eq,Ord)
instance Show IPv6 where
showsPrec p addr = showParen (p > 10)
$ showString "ipv6 "
. showHexWord16 a
. showChar ' '
. showHexWord16 b
. showChar ' '
. showHexWord16 c
. showChar ' '
. showHexWord16 d
. showChar ' '
. showHexWord16 e
. showChar ' '
. showHexWord16 f
. showChar ' '
. showHexWord16 g
. showChar ' '
. showHexWord16 h
where
(a,b,c,d,e,f,g,h) = toWord16s addr
print :: IPv6 -> IO ()
print = TIO.putStrLn . encode
showHexWord16 :: Word16 -> ShowS
showHexWord16 w =
showString "0x"
. showChar (nibbleToHex (unsafeShiftR (fromIntegral w) 12))
. showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) 8) .&. 0xF))
. showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) 4) .&. 0xF))
. showChar (nibbleToHex ((fromIntegral w) .&. 0xF))
nibbleToHex :: Word -> Char
nibbleToHex w
| w < 10 = chr (fromIntegral (w + 48))
| otherwise = chr (fromIntegral (w + 87))
instance Read IPv6 where
readPrec = parens $ prec 10 $ do
Ident "ipv6" <- lexP
a <- step readPrec
b <- step readPrec
c <- step readPrec
d <- step readPrec
e <- step readPrec
f <- step readPrec
g <- step readPrec
h <- step readPrec
return (fromWord16s a b c d e f g h)
instance Aeson.ToJSON IPv6 where
toJSON = Aeson.String . encode
instance Aeson.FromJSON IPv6 where
parseJSON = Aeson.withText "IPv6" $ \t -> case decode t of
Nothing -> fail "invalid IPv6 address"
Just i -> return i
rightToMaybe :: Either a b -> Maybe b
rightToMaybe = either (const Nothing) Just
fromOctets ::
Word8 -> Word8 -> Word8 -> Word8
-> Word8 -> Word8 -> Word8 -> Word8
-> Word8 -> Word8 -> Word8 -> Word8
-> Word8 -> Word8 -> Word8 -> Word8
-> IPv6
fromOctets a b c d e f g h i j k l m n o p =
let !(w1,w2) = fromOctetsV6
(fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)
(fromIntegral e) (fromIntegral f) (fromIntegral g) (fromIntegral h)
(fromIntegral i) (fromIntegral j) (fromIntegral k) (fromIntegral l)
(fromIntegral m) (fromIntegral n) (fromIntegral o) (fromIntegral p)
in IPv6 w1 w2
ipv6 ::
Word16 -> Word16 -> Word16 -> Word16
-> Word16 -> Word16 -> Word16 -> Word16
-> IPv6
ipv6 = fromWord16s
fromWord16s ::
Word16 -> Word16 -> Word16 -> Word16
-> Word16 -> Word16 -> Word16 -> Word16
-> IPv6
fromWord16s a b c d e f g h =
let !(w1,w2) = fromWord16sV6
(fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)
(fromIntegral e) (fromIntegral f) (fromIntegral g) (fromIntegral h)
in IPv6 w1 w2
toWord16s :: IPv6 -> (Word16,Word16,Word16,Word16,Word16,Word16,Word16,Word16)
toWord16s (IPv6 a b) =
( fromIntegral (unsafeShiftR a 48)
, fromIntegral (unsafeShiftR a 32)
, fromIntegral (unsafeShiftR a 16)
, fromIntegral a
, fromIntegral (unsafeShiftR b 48)
, fromIntegral (unsafeShiftR b 32)
, fromIntegral (unsafeShiftR b 16)
, fromIntegral b
)
fromTupleWord16s :: (Word16,Word16,Word16,Word16,Word16,Word16,Word16,Word16) -> IPv6
fromTupleWord16s (a,b,c,d,e,f,g,h) = fromWord16s a b c d e f g h
fromWord32s :: Word32 -> Word32 -> Word32 -> Word32 -> IPv6
fromWord32s a b c d =
let !(w1,w2) = fromWord32sV6
(fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)
in IPv6 w1 w2
fromTupleWord32s :: (Word32,Word32,Word32,Word32) -> IPv6
fromTupleWord32s (a,b,c,d) = fromWord32s a b c d
toWord32s :: IPv6 -> (Word32,Word32,Word32,Word32)
toWord32s (IPv6 a b) =
( fromIntegral (unsafeShiftR a 32)
, fromIntegral a
, fromIntegral (unsafeShiftR b 32)
, fromIntegral b
)
loopback :: IPv6
loopback = IPv6 0 1
any :: IPv6
any = IPv6 0 0
encode :: IPv6 -> Text
encode ip = toText [w1, w2, w3, w4, w5, w6, w7, w8]
where
(w1, w2, w3, w4, w5, w6, w7, w8) = toWord16s ip
toText ws = Text.pack $ intercalate ":" $ expand 0 longestZ grouped
where
expand _ 8 _ = ["::"]
expand _ _ [] = []
expand i longest ((x, len):wsNext)
| x == 0 && len == longest =
(if i == 0 || (i+len) == 8 then ":" else "")
: expand (i+len) 0 wsNext
| otherwise = replicate len (showHex x "") ++ expand (i+len) longest wsNext
longestZ = maximum . (0:) . map snd . filter ((==0) . fst) $ grouped
grouped = map (\x -> (head x, length x)) (group ws)
decode :: Text -> Maybe IPv6
decode t = rightToMaybe (AT.parseOnly (parser <* AT.endOfInput) t)
parser :: Atto.Parser IPv6
parser = do
s <- start
case toIPv6 s of
Nothing -> fail "Wrong number of octets in IPv6 address"
Just ip -> return ip
where
msg = "All chunks in a formatted IPv6 address must be between 0x0000 and 0xFFFF"
colonMsg = "Cannot use double colon for omitting zeroes more than once in an IPv6 address"
start = do
c <- Atto.peekChar'
if c == ':'
then go (1) 0 []
else Atto.hexadecimal >>= \w -> go (1) 1 [w]
go !colonIndex !currentIndex !ws = do
r <- do
m <- Atto.peekChar
case m of
Nothing -> return ResDone
Just c -> if c == ':'
then do
_ <- Atto.anyChar
if colonIndex == currentIndex
then fmap ResWord Atto.hexadecimal <|> pure ResDone
else do
d <- Atto.peekChar'
if d == ':'
then return ResColon
else fmap ResWord Atto.hexadecimal
else return ResDone
case r of
ResDone -> pure (S colonIndex currentIndex ws)
ResColon -> if alreadySet colonIndex
then fail colonMsg
else go currentIndex currentIndex ws
ResWord w -> restrictTo16 msg w >> go colonIndex (currentIndex + 1) (w : ws)
toIPv6 :: S -> Maybe IPv6
toIPv6 (S colonIndex total input) = case compare total 8 of
EQ -> if colonIndex == (1)
then go 0 0 input
else Nothing
GT -> Nothing
LT -> go 0 0 input
where
revColonIndex = total colonIndex
spacesToSkip = 8 total
go :: Int -> Word64 -> [Word64] -> Maybe IPv6
go !ix !acc ws = if ix > 3
then fmap (flip IPv6 acc) (go2 ix 0 ws)
else case ws of
w : wsNext -> if ix == revColonIndex
then go (ix + spacesToSkip) acc (w : wsNext)
else go (ix + 1) (acc .|. unsafeShiftL w (times16 ix)) wsNext
[] -> if ix == revColonIndex
then Just $ IPv6 0 acc
else Nothing
go2 :: Int -> Word64 -> [Word64] -> Maybe Word64
go2 !ix !acc ws = case ws of
w : wsNext -> if ix == revColonIndex
then go2 (ix + spacesToSkip) acc (w : wsNext)
else go2 (ix + 1) (acc .|. unsafeShiftL w (times16 ix 64)) wsNext
[] -> if ix == revColonIndex || ix > 7
then Just acc
else Nothing
times16 :: Int -> Int
times16 a = unsafeShiftL a 4
alreadySet :: Int -> Bool
alreadySet i = i /= (1)
restrictTo16 :: String -> Word64 -> Atto.Parser ()
restrictTo16 msg w = if w > 65535
then fail msg
else return ()
data S = S
{ _sDoubleColon :: !Int
, _sTotal :: !Int
, _sRevWords :: ![Word64]
} deriving (Show,Read)
data Res
= ResWord !Word64
| ResColon
| ResDone
fromOctetsV6 ::
Word64 -> Word64 -> Word64 -> Word64
-> Word64 -> Word64 -> Word64 -> Word64
-> Word64 -> Word64 -> Word64 -> Word64
-> Word64 -> Word64 -> Word64 -> Word64
-> (Word64,Word64)
fromOctetsV6 a b c d e f g h i j k l m n o p =
( fromOctetsWord64 a b c d e f g h
, fromOctetsWord64 i j k l m n o p
)
fromWord16sV6 ::
Word64 -> Word64 -> Word64 -> Word64
-> Word64 -> Word64 -> Word64 -> Word64
-> (Word64,Word64)
fromWord16sV6 a b c d e f g h =
( fromWord16Word64 a b c d
, fromWord16Word64 e f g h
)
fromWord32sV6 :: Word64 -> Word64 -> Word64 -> Word64 -> (Word64,Word64)
fromWord32sV6 a b c d =
( fromWord32Word64 a b
, fromWord32Word64 c d
)
fromOctetsWord64 ::
Word64 -> Word64 -> Word64 -> Word64
-> Word64 -> Word64 -> Word64 -> Word64
-> Word64
fromOctetsWord64 a b c d e f g h = fromIntegral
( shiftL a 56
.|. shiftL b 48
.|. shiftL c 40
.|. shiftL d 32
.|. shiftL e 24
.|. shiftL f 16
.|. shiftL g 8
.|. h
)
fromWord16Word64 :: Word64 -> Word64 -> Word64 -> Word64 -> Word64
fromWord16Word64 a b c d = fromIntegral
( unsafeShiftL a 48
.|. unsafeShiftL b 32
.|. unsafeShiftL c 16
.|. d
)
fromWord32Word64 :: Word64 -> Word64 -> Word64
fromWord32Word64 a b = fromIntegral (unsafeShiftL a 32 .|. b)