-- | An IPv4 address pattern has four fields separated by ".". Each -- field is either a decimal number, or a sequence inside "[]" that -- contains one or more ";"-separated decimal numbers or -- number..number ranges. -- -- Thus, any pattern field can be a sequence inside "[]", but a "[]" -- sequence cannot span multiple address fields, and a pattern field -- cannot contain both a number and a "[]" sequence at the same -- time. -- -- This means that the pattern 1.2.[3.4] is not valid (the sequence -- [3.4] cannot span two address fields) and the pattern -- 1.2.3.3[6..9] is also not valid (the last field cannot be both -- number 3 and sequence [6..9] at the same time). -- -- The syntax for IPv4 patterns is as follows: -- -- v4pattern = v4field "." v4field "." v4field "." v4field -- v4field = v4octet | "[" v4sequence "]" -- v4octet = any decimal number in the range 0 through 255 -- v4sequence = v4seq_member | v4sequence ";" v4seq_member -- v4seq_member = v4octet | v4octet ".." v4octet -- module IPv4Pattern where import Test.Tasty ( TestTree, testGroup ) import Test.Tasty.HUnit ( (@?=), testCase ) import Text.Parsec import Text.Parsec.String ( Parser ) import Text.Read ( readMaybe ) newtype IPv4Octet = IPv4Octet Int deriving (Eq, Show) data IPv4SequenceMember = IPv4SequenceMemberOctet IPv4Octet | IPv4SequenceMemberOctetRange IPv4Octet IPv4Octet deriving (Eq, Show) data IPv4Sequence = IPv4SequenceSingleMember IPv4SequenceMember | IPv4SequenceOptions IPv4SequenceMember IPv4Sequence deriving (Eq, Show) data IPv4Field = IPv4FieldOctet IPv4Octet | IPv4FieldSequence IPv4Sequence deriving (Eq, Show) data IPv4Pattern = IPv4Pattern IPv4Field IPv4Field IPv4Field IPv4Field deriving (Eq, Show) -- | Parse an IPv4 \"sequence member\". A sequence member is either an -- octet, or a start..end sequence (like an enumeration, in Haskell). -- -- ==== _Examples_ -- -- >>> parseTest v4seq_member "127" -- IPv4SequenceMemberOctet (IPv4Octet 127) -- -- >>> parseTest v4seq_member "1..5" -- IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 5) -- v4seq_member :: Parser IPv4SequenceMember v4seq_member = try both <|> just_one where both = do oct1 <- v4octet _ <- string ".." oct2 <- v4octet return $ IPv4SequenceMemberOctetRange oct1 oct2 just_one = fmap IPv4SequenceMemberOctet v4octet -- | Parse an IPv4 \"sequence\". A sequence is whatever is allowed -- within square brackets. Basically it can be three things: -- -- * An octet (number). -- * A range of addresses in start..end format. -- * An alternative, separated by a semicolon, where each side -- contains one of the previous two options. -- -- ==== _Examples_ -- -- >>> parseTest v4sequence "1" -- IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 1)) -- -- >>> parseTest v4sequence "1..2" -- IPv4SequenceSingleMember (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)) -- -- >>> parseTest v4sequence "1..2;8" -- IPv4SequenceOptions (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)) (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 8))) -- v4sequence :: Parser IPv4Sequence v4sequence = try both <|> just_one -- Maybe sepBy is appropriate here? where both = do sm <- v4seq_member _ <- char ';' s <- v4sequence return $ IPv4SequenceOptions sm s just_one = fmap IPv4SequenceSingleMember v4seq_member -- | Parse an IPv4 \"field\", which is either a boring old octet, or a -- 'v4sequence' within square brackets. -- -- ==== _Examples_ -- -- >>> parseTest v4field "127" -- IPv4FieldOctet (IPv4Octet 127) -- -- >>> parseTest v4field "[127]" -- IPv4FieldSequence (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 127))) -- v4field :: Parser IPv4Field v4field = just_octet <|> brackets where just_octet = fmap IPv4FieldOctet v4octet brackets = do _ <- char '[' s <- v4sequence _ <- char ']' return $ IPv4FieldSequence s -- | Parse an IPv4 octet, which should contain a string of digits. -- Should fail if the parsed integer does not lie between @0@ and -- @255@ inclusive. -- -- ==== _Examples_ -- -- Standard octets are parsed correctly: -- -- parseTest v4octet "0" -- IPv4Octet 0 -- -- >>> parseTest v4octet "127" -- IPv4Octet 127 -- -- >>> parseTest v4octet "255" -- IPv4Octet 255 -- -- Non-digit input throws an error: -- -- >>> parseTest v4octet "Hello, World!" -- parse error at (line 1, column 1): -- unexpected "H" -- expecting digit -- -- If we're given an integer outside the range @0..255@ (i.e. not a -- valid octet), we fail: -- -- >>> parseTest v4octet "9000" -- parse error at (line 1, column 5): -- unexpected end of input -- expecting digit -- Octet "9000" must be between 0 and 255. -- v4octet :: Parser IPv4Octet v4octet = do s <- many1 digit case ( readMaybe s :: Maybe Int ) of -- If "many1 digit" gives us a list of digits, we should be able -- to convert that to an Int! It will overflow rather than fail -- if the input is too big/small, so it should really always -- succeed. Nothing -> unexpected "readMaybe failed on a sequence of digits!" -- If we got an Int, make sure it's actually a representation of -- an octet. Just k -> if 0 <= k && k <= 255 then return (IPv4Octet k) else fail ("Octet \"" ++ (show k) ++ "\" must be between 0 and 255.") -- | Parse an ipv4 address pattern. This consists of four fields, -- separated by periods, where a field is either a simple octet or a -- sequence. -- -- See also: 'v4field', 'v4sequence'. -- -- ==== _Examples_ -- -- >>> parseTest v4pattern "127.0.0.1" -- IPv4Pattern (IPv4FieldOctet (IPv4Octet 127)) (IPv4FieldOctet (IPv4Octet 0)) (IPv4FieldOctet (IPv4Octet 0)) (IPv4FieldOctet (IPv4Octet 1)) -- v4pattern :: Parser IPv4Pattern v4pattern = do field1 <- v4field _ <- char '.' field2 <- v4field _ <- char '.' field3 <- v4field _ <- char '.' field4 <- v4field return $ IPv4Pattern field1 field2 field3 field4 -- | Enumerate the members of an 'IPv4SequenceMember'. A sequence -- member is either an octet, which is easy to enumerate -- we just -- print it -- or an octet range whose members can be enumerated -- from least to greatest. -- -- We enumerate strings instead of integers because the big picture -- is that we will be listing out patterns of ipv4 addresses, and -- those are represented as strings (dotted quad format). -- -- ==== _Examples_ -- -- >>> let (Right r) = parse v4seq_member "" "127" -- >>> sequence_members r -- ["127"] -- -- >>> let (Right r) = parse v4seq_member "" "127..135" -- >>> sequence_members r -- ["127","128","129","130","131","132","133","134","135"] -- sequence_members :: IPv4SequenceMember -> [String] sequence_members (IPv4SequenceMemberOctet (IPv4Octet i)) = [show i] sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet s) (IPv4Octet t)) = [show x | x <- [s .. t]] -- | Enumerate the members of an ipv4 sequence. These consist of -- either a single sequence member (in which case we delegate to -- 'sequence_members'), or an \"option\" which is enumerated -- recursively. -- -- ==== _Examples_ -- -- >>> let (Right r) = parse v4sequence "" "1" -- >>> sequences r -- ["1"] -- -- >>> let (Right r) = parse v4sequence "" "1..2" -- >>> sequences r -- ["1","2"] -- -- >>> let (Right r) = parse v4sequence "" "1..3;4;5..9" -- >>> sequences r -- ["1","2","3","4","5","6","7","8","9"] -- sequences :: IPv4Sequence -> [String] sequences (IPv4SequenceSingleMember sm) = sequence_members sm sequences (IPv4SequenceOptions sm s) = (sequence_members sm) ++ (sequences s) fields :: IPv4Field -> [String] fields (IPv4FieldOctet (IPv4Octet i)) = [show i] fields (IPv4FieldSequence s) = sequences s addresses :: IPv4Pattern -> [String] addresses (IPv4Pattern field1 field2 field3 field4) = do f1 <- fields field1 f2 <- fields field2 f3 <- fields field3 f4 <- fields field4 return $ f1 ++ "." ++ f2 ++ "." ++ f3 ++ "." ++ f4 -- Tests v4octet_tests :: TestTree v4octet_tests = testGroup "v4octet tests" [ test_v4octet_single_digit_parsed ] test_v4octet_single_digit_parsed :: TestTree test_v4octet_single_digit_parsed = testCase "a single digit is parsed as a v4octet" $ do -- Whatever, it's a test. let (Right actual) = parse v4octet "" "1" let expected = IPv4Octet 1 actual @?= expected