serokell-util-0.1.3.1: src/Serokell/Util/Parse/Network.hs
-- | Parsing network data
module Serokell.Util.Parse.Network
( Host (..)
, port
, ipv4address
, ipv6address
, ipv6addressWithScope
, hostname
, host
, host'
, connection
, connection'
, recipient
) where
import Control.Monad (liftM, void)
import Data.Monoid ((<>))
import Data.Word (Word16)
import Serokell.Util.Parse.Common (Parser, asciiAlphaNum, byte,
countMinMax, limitedInt)
import Text.Parsec (choice, count, many1, oneOf, option,
try, (<?>), (<|>))
import Text.ParserCombinators.Parsec.Char (alphaNum, char, hexDigit, string)
data Host = IPv4Address { hostAddress :: String }
| IPv6Address { hostAddress :: String }
| HostName { hostAddress :: String }
deriving(Show, Eq, Ord)
concatSequence :: (Monad m) => [m [a]] -> m [a]
concatSequence = liftM concat . sequence
port :: Parser Word16
port = fromIntegral <$> limitedInt 65535 "Port number to large"
ipv4address :: Parser String
ipv4address = concatSequence [
byteStr, string ".",
byteStr, string ".",
byteStr, string ".", byteStr] <?> "bad IPv4 address"
where
byteStr = show <$> byte
ipv6address :: Parser String
ipv6address = do
let ipv6variants = (try <$> skippedAtBegin)
++ [try full]
++ (try <$> skippedAtMiddle)
++ (try <$> skippedAtEnd)
++ [last2 False]
choice ipv6variants <?> "bad IPv6 address"
where
hexShortNum = countMinMax 1 4 hexDigit
h4s = (++) <$> hexShortNum <*> string ":"
sh4 = (++) <$> string ":" <*> hexShortNum
execNum 0 = return ""
execNum n = concat <$> count n h4s
partNum 0 = return ""
partNum n = do
f <- hexShortNum
e <- countMinMax 0 (n - 1) (try sh4)
return $ f ++ concat e
maybeNum n = concat <$> countMinMax 0 n h4s
last2f = try ipv4address <|> concatSequence [h4s, hexShortNum]
last2 f = if f
then last2f
else choice [try last2f,
try $ concatSequence [string "::", hexShortNum],
concatSequence [hexShortNum, string "::"]]
skippedAtBegin =
map (\i -> concatSequence [string "::", execNum i, last2 True]) [5,4..0]
skippedAtMiddle = [
concatSequence [partNum 1, string "::", maybeNum 4, last2 True],
concatSequence [partNum 2, string "::", maybeNum 3, last2 True],
concatSequence [partNum 3, string "::", maybeNum 2, last2 True],
concatSequence [partNum 4, string "::", maybeNum 1, last2 True],
concatSequence [partNum 5, string "::", last2 True],
concatSequence [partNum 6, string "::", hexShortNum]]
skippedAtEnd = [concatSequence [partNum 7, string "::"]]
full = concatSequence [concat <$> count 6 h4s, last2 True]
ipv6addressWithScope :: Parser String
ipv6addressWithScope = concatSequence [ipv6address, option "" scope]
where
scope = concatSequence [string "%", many1 asciiAlphaNum]
hostname :: Parser String
hostname = many1 $ alphaNum <|> oneOf ".-_"
host :: Parser String
host = hostAddress <$> host'
host' :: Parser Host
host' = (IPv6Address <$> try ipv6str)
<|> (IPv4Address <$> try ipv4address)
<|> (HostName <$> hostname)
where
ipv6str = do
void $ char '['
ipv6 <- ipv6addressWithScope
void $ char ']'
return ipv6
connection' :: Parser (Host, Maybe Word16)
connection' = do
addr <- host'
p <- maybePort
return (addr, p)
where
maybePort = option Nothing $ char ':' >> Just <$> port
connection :: Parser (String, Maybe Word16)
connection = (\(h, p) -> (hostAddress h, p)) <$> connection'
-- | 'Parser' for host with both hostname and port.
-- Example: 54.122.0.255:9999
recipient :: Parser (String, Word16)
recipient = connection >>= \(h, mp) -> case mp of
Just p -> pure (h, p)
_ -> fail $ "No port specified for host " <> h