]> gitweb.michael.orlitzky.com - dead/harbl.git/blob - src/IPv4Pattern.hs
dd95b13be37d4563e064428c0b52ea109d6c49b6
[dead/harbl.git] / src / IPv4Pattern.hs
1 -- | An IPv4 address pattern has four fields separated by ".". Each
2 -- field is either a decimal number, or a sequence inside "[]" that
3 -- contains one or more ";"-separated decimal numbers or
4 -- number..number ranges.
5 --
6 -- Thus, any pattern field can be a sequence inside "[]", but a "[]"
7 -- sequence cannot span multiple address fields, and a pattern field
8 -- cannot contain both a number and a "[]" sequence at the same
9 -- time.
10 --
11 -- This means that the pattern 1.2.[3.4] is not valid (the sequence
12 -- [3.4] cannot span two address fields) and the pattern
13 -- 1.2.3.3[6..9] is also not valid (the last field cannot be both
14 -- number 3 and sequence [6..9] at the same time).
15 --
16 -- The syntax for IPv4 patterns is as follows:
17 --
18 -- v4pattern = v4field "." v4field "." v4field "." v4field
19 -- v4field = v4octet | "[" v4sequence "]"
20 -- v4octet = any decimal number in the range 0 through 255
21 -- v4sequence = v4seq_member | v4sequence ";" v4seq_member
22 -- v4seq_member = v4octet | v4octet ".." v4octet
23 --
24 module IPv4Pattern
25 where
26
27
28 import Test.Tasty ( TestTree, testGroup )
29 import Test.Tasty.HUnit ( (@?=), testCase )
30 import Text.Parsec (
31 (<|>),
32 char,
33 digit,
34 many1,
35 parse,
36 string,
37 try,
38 unexpected )
39 import Text.Parsec.String ( Parser )
40 import Text.Read ( readMaybe )
41
42 import Pretty ( Pretty(..) )
43
44
45 -- * Octets
46
47 -- | An ipv4 octet; that is, an integer between @0@ and @255@
48 -- inclusive. This is the data type corresponding to a \"v4octet\"
49 -- in the postscreen parser.
50 --
51 newtype IPv4Octet = IPv4Octet Int
52 deriving (Eq, Show)
53
54
55 instance Pretty IPv4Octet where
56 pretty_show (IPv4Octet x) = show x
57
58
59 -- | Parse an IPv4 octet, which should contain a string of digits.
60 -- Should fail if the parsed integer does not lie between @0@ and
61 -- @255@ inclusive.
62 --
63 -- ==== _Examples_
64 --
65 -- >>> import Text.Parsec ( parseTest )
66 --
67 -- Standard octets are parsed correctly:
68 --
69 -- >>> parseTest v4octet "0"
70 -- IPv4Octet 0
71 --
72 -- >>> parseTest v4octet "127"
73 -- IPv4Octet 127
74 --
75 -- >>> parseTest v4octet "255"
76 -- IPv4Octet 255
77 --
78 -- Non-digit input throws an error:
79 --
80 -- >>> parseTest v4octet "Hello, World!"
81 -- parse error at (line 1, column 1):
82 -- unexpected "H"
83 -- expecting digit
84 --
85 -- If we're given an integer outside the range @0..255@ (i.e. not a
86 -- valid octet), we fail:
87 --
88 -- >>> parseTest v4octet "9000"
89 -- parse error at (line 1, column 5):
90 -- unexpected end of input
91 -- expecting digit
92 -- Octet "9000" must be between 0 and 255.
93 --
94 v4octet :: Parser IPv4Octet
95 v4octet = do
96 s <- many1 digit
97 case ( readMaybe s :: Maybe Int ) of
98 -- If "many1 digit" gives us a list of digits, we should be able
99 -- to convert that to an Int! It will overflow rather than fail
100 -- if the input is too big/small, so it should really always
101 -- succeed.
102 Nothing -> unexpected "v4octet: readMaybe failed on a sequence of digits!"
103
104 -- If we got an Int, make sure it's actually a representation of
105 -- an octet.
106 Just k -> if 0 <= k && k <= 255
107 then return (IPv4Octet k)
108 else fail ("Octet \"" ++ (show k)
109 ++ "\" must be between 0 and 255.")
110
111
112
113
114 -- * Sequence members
115
116
117 -- | An ipv4 \"sequence member\". A sequence member is either an
118 -- integer (an octet) or a range of integers (contained in an
119 -- octet). This data type corresponds to \"v4seq_member\" in the
120 -- postscreen parser.
121 --
122 data IPv4SequenceMember =
123 IPv4SequenceMemberOctet IPv4Octet
124 | IPv4SequenceMemberOctetRange IPv4Octet IPv4Octet
125 deriving (Eq, Show)
126
127
128 instance Pretty IPv4SequenceMember where
129 pretty_show (IPv4SequenceMemberOctet octet) = pretty_show octet
130 pretty_show (IPv4SequenceMemberOctetRange octet1 octet2) =
131 (pretty_show octet1) ++ ".." ++ (pretty_show octet2)
132
133
134 -- | Parse an IPv4 \"sequence member\". A sequence member is either an
135 -- octet, or a start..end sequence (like an enumeration, in Haskell).
136 --
137 -- ==== _Examples_
138 --
139 -- >>> import Text.Parsec ( parseTest )
140 --
141 -- >>> parseTest v4seq_member "127"
142 -- IPv4SequenceMemberOctet (IPv4Octet 127)
143 --
144 -- >>> parseTest v4seq_member "1..5"
145 -- IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 5)
146 --
147 v4seq_member :: Parser IPv4SequenceMember
148 v4seq_member = try both <|> just_one
149 where
150 both = do
151 oct1 <- v4octet
152 _ <- string ".."
153 oct2 <- v4octet
154 return $ IPv4SequenceMemberOctetRange oct1 oct2
155
156 just_one = fmap IPv4SequenceMemberOctet v4octet
157
158
159
160 -- * Sequences
161
162 -- | An ipv4 \"sequence\". A sequence contains either a single
163 -- \"sequence member\" (see 'IPv4SequenceMember'), or a sequence
164 -- member along with another sequence. So, this is a potentially
165 -- recursive definition. This type corresponds to \"v4sequence\" in
166 -- the postscreen parser.
167 --
168 data IPv4Sequence =
169 IPv4SequenceSingleMember IPv4SequenceMember
170 | IPv4SequenceOptions IPv4SequenceMember IPv4Sequence
171 deriving (Eq, Show)
172
173
174 instance Pretty IPv4Sequence where
175 pretty_show (IPv4SequenceSingleMember member) = pretty_show member
176 pretty_show (IPv4SequenceOptions member subsequence) =
177 (pretty_show member) ++ ";" ++ (pretty_show subsequence)
178
179
180 -- | Parse an IPv4 \"sequence\". A sequence is whatever is allowed
181 -- within square brackets. Basically it can be three things:
182 --
183 -- * An octet (number).
184 -- * A range of addresses in start..end format.
185 -- * An alternative, separated by a semicolon, where each side
186 -- contains one of the previous two options.
187 --
188 -- ==== _Examples_
189 --
190 -- >>> import Text.Parsec ( parseTest )
191 -- >>> parseTest v4sequence "1"
192 -- IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 1))
193 --
194 -- >>> pretty_print $ parse v4sequence "" "1..2"
195 -- 1..2
196 --
197 -- >>> pretty_print $ parse v4sequence "" "1..2;8"
198 -- 1..2;8
199 --
200 v4sequence :: Parser IPv4Sequence
201 v4sequence = try both <|> just_one
202 where
203 both = do
204 sm <- v4seq_member
205 _ <- char ';'
206 s <- v4sequence
207 return $ IPv4SequenceOptions sm s
208
209 just_one = fmap IPv4SequenceSingleMember v4seq_member
210
211
212
213 -- * Fields
214
215 data IPv4Field = IPv4FieldOctet IPv4Octet | IPv4FieldSequence IPv4Sequence
216 deriving (Eq, Show)
217
218
219 instance Pretty IPv4Field where
220 pretty_show (IPv4FieldOctet octet) = pretty_show octet
221 pretty_show (IPv4FieldSequence s) = "[" ++ (pretty_show s) ++ "]"
222
223
224 -- | Parse an IPv4 \"field\", which is either a boring old octet, or a
225 -- 'v4sequence' within square brackets.
226 --
227 -- ==== _Examples_
228 --
229 -- >>> import Text.Parsec ( parseTest )
230 -- >>> parseTest v4field "127"
231 -- IPv4FieldOctet (IPv4Octet 127)
232 --
233 -- >>> pretty_print $ parse v4field "" "[127]"
234 -- [127]
235 --
236 v4field :: Parser IPv4Field
237 v4field = just_octet <|> brackets
238 where
239 just_octet = fmap IPv4FieldOctet v4octet
240
241 brackets = do
242 _ <- char '['
243 s <- v4sequence
244 _ <- char ']'
245 return $ IPv4FieldSequence s
246
247
248
249 -- * Patterns
250
251 data IPv4Pattern =
252 IPv4Pattern IPv4Field IPv4Field IPv4Field IPv4Field
253 deriving (Eq, Show)
254
255
256 instance Pretty IPv4Pattern where
257 pretty_show (IPv4Pattern f1 f2 f3 f4) =
258 (pretty_show f1) ++ "."
259 ++ (pretty_show f2)
260 ++ "."
261 ++ (pretty_show f3)
262 ++ "."
263 ++ (pretty_show f4)
264
265
266 -- | Parse an ipv4 address pattern. This consists of four fields,
267 -- separated by periods, where a field is either a simple octet or a
268 -- sequence.
269 --
270 -- See also: 'v4field', 'v4sequence'.
271 --
272 -- ==== _Examples_
273 --
274 -- >>> pretty_print $ parse v4pattern "" "127.0.0.1"
275 -- 127.0.0.1
276 --
277 -- >>> pretty_print $ parse v4pattern "" "127.0.[1..3].1"
278 -- 127.0.[1..3].1
279 --
280 -- >>> pretty_print $ parse v4pattern "" "127.0.[1..3;8].1"
281 -- 127.0.[1..3;8].1
282 --
283 -- In the module intro, it is mentioned that this is invalid:
284 --
285 -- >>> import Text.Parsec ( parseTest )
286 -- >>> parseTest v4pattern "1.2.[3.4]"
287 -- parse error at (line 1, column 7):
288 -- unexpected "."
289 -- expecting digit or "]"
290 --
291 -- This one is /also/ invalid; however, we'll parse the valid part off
292 -- the front of it:
293 --
294 -- >>> pretty_print $ parse v4pattern "" "1.2.3.3[6..9]"
295 -- 1.2.3.3
296 --
297 v4pattern :: Parser IPv4Pattern
298 v4pattern = do
299 field1 <- v4field
300 _ <- char '.'
301 field2 <- v4field
302 _ <- char '.'
303 field3 <- v4field
304 _ <- char '.'
305 field4 <- v4field
306 return $ IPv4Pattern field1 field2 field3 field4
307
308
309
310 -- * Enumeration
311
312 -- | Enumerate the members of an 'IPv4SequenceMember'. A sequence
313 -- member is either an octet, which is easy to enumerate -- we just
314 -- print it -- or an octet range whose members can be enumerated
315 -- from least to greatest.
316 --
317 -- We enumerate strings instead of integers because the big picture
318 -- is that we will be listing out patterns of ipv4 addresses, and
319 -- those are represented as strings (dotted quad format).
320 --
321 -- ==== _Examples_
322 --
323 -- >>> let (Right r) = parse v4seq_member "" "127"
324 -- >>> sequence_members r
325 -- ["127"]
326 --
327 -- >>> let (Right r) = parse v4seq_member "" "127..135"
328 -- >>> sequence_members r
329 -- ["127","128","129","130","131","132","133","134","135"]
330 --
331 sequence_members :: IPv4SequenceMember -> [String]
332 sequence_members (IPv4SequenceMemberOctet (IPv4Octet i)) = [show i]
333 sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet s) (IPv4Octet t)) =
334 [show x | x <- [s .. t]]
335
336
337 -- | Enumerate the members of an ipv4 sequence. These consist of
338 -- either a single sequence member (in which case we delegate to
339 -- 'sequence_members'), or an \"option\" which is enumerated
340 -- recursively.
341 --
342 -- ==== _Examples_
343 --
344 -- >>> let (Right r) = parse v4sequence "" "1"
345 -- >>> sequences r
346 -- ["1"]
347 --
348 -- >>> let (Right r) = parse v4sequence "" "1..2"
349 -- >>> sequences r
350 -- ["1","2"]
351 --
352 -- >>> let (Right r) = parse v4sequence "" "1..3;4;5..9"
353 -- >>> sequences r
354 -- ["1","2","3","4","5","6","7","8","9"]
355 --
356 sequences :: IPv4Sequence -> [String]
357 sequences (IPv4SequenceSingleMember sm) =
358 sequence_members sm
359 sequences (IPv4SequenceOptions sm s) =
360 (sequence_members sm) ++ (sequences s)
361
362
363 -- | Enumerate the members of an 'IPv4Field'. If the field contains a
364 -- single 'IPv4Octet', we simply 'show' it. Otherwise it contains an
365 -- 'IPv4FieldSequence', and we enumerate that recursively using
366 -- 'sequences'.
367 --
368 -- ==== _Examples_
369 --
370 -- >>> let (Right r) = parse v4field "" "1"
371 -- >>> fields r
372 -- ["1"]
373 --
374 -- >>> let (Right r) = parse v4field "" "[127..135]"
375 -- >>> fields r
376 -- ["127","128","129","130","131","132","133","134","135"]
377 --
378 fields :: IPv4Field -> [String]
379 fields (IPv4FieldOctet (IPv4Octet i)) = [show i]
380 fields (IPv4FieldSequence s) = sequences s
381
382
383 -- | Enumerate the addresses represented by a given 'IPv4Pattern'.
384 --
385 -- A pattern contains four fields, sepearated by period
386 -- characters. We want to list all possible combinations of
387 -- addresses where the first octet comes from the first field, the
388 -- second octet comes from the second field... and so on. To do
389 -- this, we take advantage of the List monad and the fact that
390 -- 'fields' returns a list of 'String's.
391 --
392 -- ==== _Examples_
393 --
394 -- A single address:
395 --
396 -- >>> let (Right r) = parse v4pattern "" "127.0.0.1"
397 -- >>> addresses r
398 -- ["127.0.0.1"]
399 --
400 -- Anything between 127.0.0.2 and 127.0.0.4, and either 127.0.0.10
401 -- or 127.0.0.11:
402 --
403 -- >>> let (Right r) = parse v4pattern "" "127.0.0.[2..4;10;11]"
404 -- >>> addresses r
405 -- ["127.0.0.2","127.0.0.3","127.0.0.4","127.0.0.10","127.0.0.11"]
406 --
407 addresses :: IPv4Pattern -> [String]
408 addresses (IPv4Pattern field1 field2 field3 field4) = do
409 f1 <- fields field1
410 f2 <- fields field2
411 f3 <- fields field3
412 f4 <- fields field4
413 return $ f1 ++ "." ++ f2 ++ "." ++ f3 ++ "." ++ f4
414
415
416
417 -- * Tests
418
419 v4octet_tests :: TestTree
420 v4octet_tests =
421 testGroup
422 "v4octet tests"
423 [ test_v4octet_single_digit_parsed ]
424
425 test_v4octet_single_digit_parsed :: TestTree
426 test_v4octet_single_digit_parsed =
427 testCase "a single digit is parsed as a v4octet" $ do
428 -- Whatever, it's a test.
429 let (Right actual) = parse v4octet "" "1"
430 let expected = IPv4Octet 1
431 actual @?= expected