]> gitweb.michael.orlitzky.com - hath.git/blob - src/IPv4Address.hs
Switch from test-framework to tasty.
[hath.git] / src / IPv4Address.hs
1 module IPv4Address(
2 IPv4Address(..),
3 ipv4address_properties,
4 ipv4address_tests,
5 most_sig_bit_different )
6 where
7
8
9 import Test.QuickCheck ( Gen ) -- Not re-exported by tasty
10 import Test.Tasty ( TestTree, testGroup )
11 import Test.Tasty.HUnit ( (@?=), testCase )
12 import Test.Tasty.QuickCheck (
13 Arbitrary(..),
14 Property,
15 (==>),
16 testProperty )
17
18 import Maskable (Maskable(..))
19 import Maskbits (Maskbits(..))
20 import Octet (Octet(..))
21
22 data IPv4Address =
23 IPv4Address { octet1 :: Octet,
24 octet2 :: Octet,
25 octet3 :: Octet,
26 octet4 :: Octet }
27 deriving (Eq)
28
29
30 instance Show IPv4Address where
31 show addr = concat [(show oct1) ++ ".",
32 (show oct2) ++ ".",
33 (show oct3) ++ ".",
34 (show oct4)]
35 where
36 oct1 = (octet1 addr)
37 oct2 = (octet2 addr)
38 oct3 = (octet3 addr)
39 oct4 = (octet4 addr)
40
41
42 instance Arbitrary IPv4Address where
43 arbitrary = do
44 oct1 <- arbitrary :: Gen Octet
45 oct2 <- arbitrary :: Gen Octet
46 oct3 <- arbitrary :: Gen Octet
47 oct4 <- arbitrary :: Gen Octet
48 return (IPv4Address oct1 oct2 oct3 oct4)
49
50
51
52 instance Maskable IPv4Address where
53
54 apply_mask addr mask bit =
55 apply_mask' mask
56 where
57 oct1 = octet1 addr
58 oct2 = octet2 addr
59 oct3 = octet3 addr
60 oct4 = octet4 addr
61
62 -- A copy of 'addr' with the fourth octet zeroed (or oned).
63 new_addr1 = addr { octet4 = (apply_mask oct4 Zero bit) }
64
65 -- Likewise for new_addr1's third octet.
66 new_addr2 = new_addr1 { octet3 = (apply_mask oct3 Zero bit) }
67
68 -- And new_addr2's second octet.
69 new_addr3 = new_addr2 { octet2 = (apply_mask oct2 Zero bit) }
70
71 -- This helper function allows us to pattern-match cleanly.
72 apply_mask' :: Maskbits -> IPv4Address
73
74 apply_mask' ThirtyTwo = addr
75
76 apply_mask' ThirtyOne = addr { octet4 = (apply_mask oct4 Seven bit) }
77
78 apply_mask' Thirty =
79 addr { octet4 = (apply_mask oct4 Six bit) }
80
81 apply_mask' TwentyNine =
82 addr { octet4 = (apply_mask oct4 Five bit) }
83
84 apply_mask' TwentyEight =
85 addr { octet4 = (apply_mask oct4 Four bit) }
86
87 apply_mask' TwentySeven =
88 addr { octet4 = (apply_mask oct4 Three bit) }
89
90 apply_mask' TwentySix =
91 addr { octet4 = (apply_mask oct4 Two bit) }
92
93 apply_mask' TwentyFive =
94 addr { octet4 = (apply_mask oct4 One bit) }
95
96 apply_mask' TwentyFour = new_addr1
97
98 apply_mask' TwentyThree =
99 new_addr1 { octet3 = (apply_mask oct3 Seven bit) }
100
101 apply_mask' TwentyTwo =
102 new_addr1 { octet3 = (apply_mask oct3 Six bit) }
103
104 apply_mask' TwentyOne =
105 new_addr1 { octet3 = (apply_mask oct3 Five bit) }
106
107 apply_mask' Twenty =
108 new_addr1 { octet3 = (apply_mask oct3 Four bit) }
109
110 apply_mask' Nineteen =
111 new_addr1 { octet3 = (apply_mask oct3 Three bit) }
112
113 apply_mask' Eighteen =
114 new_addr1 { octet3 = (apply_mask oct3 Two bit) }
115
116 apply_mask' Seventeen =
117 new_addr1 { octet3 = (apply_mask oct3 One bit) }
118
119 apply_mask' Sixteen =
120 new_addr2
121
122 apply_mask' Fifteen =
123 new_addr2 { octet2 = (apply_mask oct2 Seven bit) }
124
125 apply_mask' Fourteen =
126 new_addr2 { octet2 = (apply_mask oct2 Six bit) }
127
128 apply_mask' Thirteen =
129 new_addr2 { octet2 = (apply_mask oct2 Five bit) }
130
131 apply_mask' Twelve =
132 new_addr2 { octet2 = (apply_mask oct2 Four bit) }
133
134 apply_mask' Eleven =
135 new_addr2 { octet2 = (apply_mask oct2 Three bit) }
136
137 apply_mask' Ten =
138 new_addr2 { octet2 = (apply_mask oct2 Two bit) }
139
140 apply_mask' Nine =
141 new_addr2 { octet2 = (apply_mask oct2 One bit) }
142
143 apply_mask' Eight =
144 new_addr3 { octet2 = (apply_mask oct2 Zero bit) }
145
146 apply_mask' Seven =
147 new_addr3 { octet1 = (apply_mask oct1 Seven bit) }
148
149 apply_mask' Six =
150 new_addr3 { octet1 = (apply_mask oct1 Six bit) }
151
152 apply_mask' Five =
153 new_addr3 { octet1 = (apply_mask oct1 Five bit) }
154
155 apply_mask' Four =
156 new_addr3 { octet1 = (apply_mask oct1 Four bit) }
157
158 apply_mask' Three =
159 new_addr3 { octet1 = (apply_mask oct1 Three bit) }
160
161 apply_mask' Two =
162 new_addr3 { octet1 = (apply_mask oct1 Two bit) }
163
164 apply_mask' One =
165 new_addr3 { octet1 = (apply_mask oct1 One bit) }
166
167 apply_mask' Zero =
168 new_addr3 { octet1 = (apply_mask oct1 Zero bit) }
169
170
171 instance Bounded IPv4Address where
172 -- | The minimum possible IPv4 address, 0.0.0.0.
173 minBound = IPv4Address minBound minBound minBound minBound
174
175 -- | The maximum possible IPv4 address, 255.255.255.255.
176 maxBound = IPv4Address maxBound maxBound maxBound maxBound
177
178
179
180
181 instance Enum IPv4Address where
182 -- | Convert an 'Int' @x@ to an 'IPv4Address'. Each octet of @x@ is
183 -- right-shifted by the appropriate number of bits, and the fractional
184 -- part is dropped.
185 toEnum x =
186 IPv4Address oct1 oct2 oct3 oct4
187 where
188 -- Chop off the higher octets. x1 = x `mod` 2^32, would be
189 -- redundant.
190 x2 = x `mod` 2^(24 :: Integer)
191 x3 = x `mod` 2^(16 :: Integer)
192 x4 = x `mod` 2^(8 :: Integer)
193 -- Perform right-shifts. x4 doesn't need a shift.
194 shifted_x1 = x `quot` 2^(24 :: Integer)
195 shifted_x2 = x2 `quot` 2^(16 :: Integer)
196 shifted_x3 = x3 `quot` 2^(8 :: Integer)
197 oct1 = toEnum shifted_x1
198 oct2 = toEnum shifted_x2
199 oct3 = toEnum shifted_x3
200 oct4 = toEnum x4
201
202 -- | Convert @addr@ to an 'Int' by converting each octet to an 'Int'
203 -- and shifting the result to the left by 0,8.16, or 24 bits.
204 fromEnum addr =
205 (shifted_oct1) + (shifted_oct2) + (shifted_oct3) + oct4
206 where
207 oct1 = fromEnum (octet1 addr)
208 oct2 = fromEnum (octet2 addr)
209 oct3 = fromEnum (octet3 addr)
210 oct4 = fromEnum (octet4 addr)
211 shifted_oct1 = oct1 * 2^(24 :: Integer)
212 shifted_oct2 = oct2 * 2^(16 :: Integer)
213 shifted_oct3 = oct3 * 2^(8 :: Integer)
214
215 -- | Given two addresses, find the number of the most significant bit
216 -- where they differ. If the addresses are the same, return
217 -- Maskbits.Zero.
218 most_sig_bit_different :: IPv4Address -> IPv4Address -> Maskbits
219 most_sig_bit_different addr1 addr2
220 | addr1 == addr2 = Maskbits.Zero
221 | m1 /= n1 = Maskbits.One
222 | m2 /= n2 = Two
223 | m3 /= n3 = Three
224 | m4 /= n4 = Four
225 | m5 /= n5 = Five
226 | m6 /= n6 = Six
227 | m7 /= n7 = Seven
228 | m8 /= n8 = Eight
229 | m9 /= n9 = Nine
230 | m10 /= n10 = Ten
231 | m11 /= n11 = Eleven
232 | m12 /= n12 = Twelve
233 | m13 /= n13 = Thirteen
234 | m14 /= n14 = Fourteen
235 | m15 /= n15 = Fifteen
236 | m16 /= n16 = Sixteen
237 | m17 /= n17 = Seventeen
238 | m18 /= n18 = Eighteen
239 | m19 /= n19 = Nineteen
240 | m20 /= n20 = Twenty
241 | m21 /= n21 = TwentyOne
242 | m22 /= n22 = TwentyTwo
243 | m23 /= n23 = TwentyThree
244 | m24 /= n24 = TwentyFour
245 | m25 /= n25 = TwentyFive
246 | m26 /= n26 = TwentySix
247 | m27 /= n27 = TwentySeven
248 | m28 /= n28 = TwentyEight
249 | m29 /= n29 = TwentyNine
250 | m30 /= n30 = Thirty
251 | m31 /= n31 = ThirtyOne
252 | m32 /= n32 = ThirtyTwo
253 | otherwise = Maskbits.Zero
254 where
255 m1 = (b1 oct1a)
256 m2 = (b2 oct1a)
257 m3 = (b3 oct1a)
258 m4 = (b4 oct1a)
259 m5 = (b5 oct1a)
260 m6 = (b6 oct1a)
261 m7 = (b7 oct1a)
262 m8 = (b8 oct1a)
263 m9 = (b1 oct2a)
264 m10 = (b2 oct2a)
265 m11 = (b3 oct2a)
266 m12 = (b4 oct2a)
267 m13 = (b5 oct2a)
268 m14 = (b6 oct2a)
269 m15 = (b7 oct2a)
270 m16 = (b8 oct2a)
271 m17 = (b1 oct3a)
272 m18 = (b2 oct3a)
273 m19 = (b3 oct3a)
274 m20 = (b4 oct3a)
275 m21 = (b5 oct3a)
276 m22 = (b6 oct3a)
277 m23 = (b7 oct3a)
278 m24 = (b8 oct3a)
279 m25 = (b1 oct4a)
280 m26 = (b2 oct4a)
281 m27 = (b3 oct4a)
282 m28 = (b4 oct4a)
283 m29 = (b5 oct4a)
284 m30 = (b6 oct4a)
285 m31 = (b7 oct4a)
286 m32 = (b8 oct4a)
287 oct1a = (octet1 addr1)
288 oct2a = (octet2 addr1)
289 oct3a = (octet3 addr1)
290 oct4a = (octet4 addr1)
291 n1 = (b1 oct1b)
292 n2 = (b2 oct1b)
293 n3 = (b3 oct1b)
294 n4 = (b4 oct1b)
295 n5 = (b5 oct1b)
296 n6 = (b6 oct1b)
297 n7 = (b7 oct1b)
298 n8 = (b8 oct1b)
299 n9 = (b1 oct2b)
300 n10 = (b2 oct2b)
301 n11 = (b3 oct2b)
302 n12 = (b4 oct2b)
303 n13 = (b5 oct2b)
304 n14 = (b6 oct2b)
305 n15 = (b7 oct2b)
306 n16 = (b8 oct2b)
307 n17 = (b1 oct3b)
308 n18 = (b2 oct3b)
309 n19 = (b3 oct3b)
310 n20 = (b4 oct3b)
311 n21 = (b5 oct3b)
312 n22 = (b6 oct3b)
313 n23 = (b7 oct3b)
314 n24 = (b8 oct3b)
315 n25 = (b1 oct4b)
316 n26 = (b2 oct4b)
317 n27 = (b3 oct4b)
318 n28 = (b4 oct4b)
319 n29 = (b5 oct4b)
320 n30 = (b6 oct4b)
321 n31 = (b7 oct4b)
322 n32 = (b8 oct4b)
323 oct1b = (octet1 addr2)
324 oct2b = (octet2 addr2)
325 oct3b = (octet3 addr2)
326 oct4b = (octet4 addr2)
327
328
329 -- Test lists.
330 ipv4address_tests :: TestTree
331 ipv4address_tests =
332 testGroup "IPv4 Address Tests" [
333 test_enum,
334 test_maxBound,
335 test_minBound,
336 test_most_sig_bit_different1,
337 test_most_sig_bit_different2,
338 test_to_enum ]
339
340 ipv4address_properties :: TestTree
341 ipv4address_properties =
342 testGroup
343 "IPv4 Address Properties "
344 [ prop_from_enum_to_enum_inverses ]
345
346 -- QuickCheck properties
347 prop_from_enum_to_enum_inverses :: TestTree
348 prop_from_enum_to_enum_inverses =
349 testProperty "fromEnum and toEnum are inverses" prop
350 where
351 prop :: Int -> Property
352 prop x =
353 (0 <= x) && (x <= 2^(32 :: Integer) - 1) ==>
354 fromEnum (toEnum x :: IPv4Address) == x
355
356 -- HUnit Tests
357 mk_testaddr :: Int -> Int -> Int -> Int -> IPv4Address
358 mk_testaddr a b c d =
359 IPv4Address oct1 oct2 oct3 oct4
360 where
361 oct1 = toEnum a
362 oct2 = toEnum b
363 oct3 = toEnum c
364 oct4 = toEnum d
365
366
367 test_minBound :: TestTree
368 test_minBound =
369 testCase desc $ actual @?= expected
370 where
371 desc = "minBound should be 0.0.0.0"
372 expected = mk_testaddr 0 0 0 0
373 actual = minBound :: IPv4Address
374
375
376 test_maxBound :: TestTree
377 test_maxBound =
378 testCase desc $ actual @?= expected
379 where
380 desc = "maxBound should be 255.255.255.255"
381 expected = mk_testaddr 255 255 255 255
382 actual = maxBound :: IPv4Address
383
384
385 test_enum :: TestTree
386 test_enum =
387 testCase desc $ actual @?= expected
388 where
389 desc = "enumerating a /24 gives the correct addresses"
390 expected = ["192.168.0." ++ (show x) | x <- [0..255::Int] ]
391 lb = mk_testaddr 192 168 0 0
392 ub = mk_testaddr 192 168 0 255
393 actual = map show [lb..ub]
394
395
396 test_most_sig_bit_different1 :: TestTree
397 test_most_sig_bit_different1 =
398 testCase desc $ actual @?= expected
399 where
400 desc = "10.1.1.0 and 10.1.0.0 differ in bit 24"
401 addr1 = mk_testaddr 10 1 1 0
402 addr2 = (mk_testaddr 10 1 0 0)
403 expected = TwentyFour
404 actual = most_sig_bit_different addr1 addr2
405
406
407
408 test_most_sig_bit_different2 :: TestTree
409 test_most_sig_bit_different2 =
410 testCase desc $ actual @?= expected
411 where
412 desc = "10.1.2.0 and 10.1.1.0 differ in bit 23"
413 addr1 = mk_testaddr 10 1 2 0
414 addr2 = mk_testaddr 10 1 1 0
415 expected = TwentyThree
416 actual = most_sig_bit_different addr1 addr2
417
418
419 test_to_enum :: TestTree
420 test_to_enum =
421 testCase desc $ actual @?= expected
422 where
423 desc = "192.168.0.0 in base-10 is 3232235520"
424 expected = mk_testaddr 192 168 0 0
425 actual = toEnum 3232235520