packages feed

rws-0.1.1.2: src/IPv4.hs

{-# LANGUAGE CPP #-}
module IPv4 where

import Data.Binary.Put
import Data.Bits
import qualified Data.ByteString.Lazy as B
import Data.Word
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Perm

import Util
import Packet
import Lexer

#ifdef HRWS_TEST
import Debug.Trace
import Test.QuickCheck hiding ((.&.))
import Test.Framework (Test)
import Test.Framework.Providers.QuickCheck2 (testProperty)
#endif


parseIPv4Ver :: Parser Word8 
parseIPv4Ver = parseIntAttribute "ver"

parseIPv4Hlen :: Parser Word8
parseIPv4Hlen = parseIntAttribute "hlen"

parseIPv4Tos :: Parser Word8
parseIPv4Tos = parseIntAttribute "tos"

parseIPv4Len :: Parser Word16
parseIPv4Len = parseIntAttribute "len"

parseIPv4Id :: Parser Word16
parseIPv4Id = parseIntAttribute "id"

parseIPv4Ttl :: Parser Word8
parseIPv4Ttl = parseIntAttribute "ttl"

parseIPv4Prot :: Parser Word8
parseIPv4Prot = parseIntAttribute "prot"

parseIPv4Csum :: Parser Word16
parseIPv4Csum = parseIntAttribute "csum"

parseIPv4Offset :: Parser Word16
parseIPv4Offset = parseIntAttribute "off"

parseIPv4Pkt :: Parser Packet -> Parser IPv4Pkt
parseIPv4Pkt f = permute
  (tuple <$?> (0xa0a0a0a1, parseIPv4Address "src")
         <|?> (0xa0a0a0a2, parseIPv4Address "dst")
         <|?> (4, parseIPv4Ver)
         <|?> (5, parseIPv4Hlen)
         <|?> (0, parseIPv4Len)
         <|?> (0x1234, parseIPv4Id)
         <|?> (17, parseIPv4Prot)
         <|?> (0, try parseIPv4Tos)
         <|?> (64, try parseIPv4Ttl)
         <|?> (0, parseIPv4Csum)
         <|?> (0, parseIPv4Offset)
         <|?> ([PPayload defaultPayload], parsePayload f)
         <|?> (True, parseBoolAttribute "fixcsum"))
  where
    tuple s d v hl l id_ p tos ttl csum off pl cc =
      IPv4Pkt (IPv4 v hl tos l id_ off ttl p csum s d cc) pl

ipv4Decl :: Parser Packet -> Parser Packet
ipv4Decl f = do
 symbol "ipv4"
 p <- parseIPv4Pkt f
 return (PIPv4 p)

pseudoIpv4Write :: IPv4 -> Word16 -> Put
pseudoIpv4Write p l = do
    putWord32be (ipv4Src p)
    putWord32be (ipv4Dst p)
    putWord8    0
    putWord8    (ipv4Prot p)
    putWord16be l

ipv4WriteHdr :: IPv4 -> Put
ipv4WriteHdr p = do
    putWord8    ((ipv4Ver p `shiftL` 4) .|. ipv4Hlen p)
    putWord8    (ipv4Tos p)
    putWord16be (ipv4Len p)
    putWord16be (ipv4Id p)
    putWord16be (ipv4Offset p)
    putWord8    (ipv4Ttl p)
    putWord8    (ipv4Prot p)
    putWord16be (ipv4Csum p)
    putWord32be    (ipv4Src p)
    putWord32be    (ipv4Dst p)

instance PacketWriteable IPv4Pkt where packetWrite p = ipv4Write $ ipv4PktHeader p

ipv4Write :: IPv4 -> Maybe Packet -> B.ByteString -> Put
ipv4Write h _ bs = do
    let hdr = h {ipv4Len = fromIntegral $ 20 + B.length bs}
    let sum32 = if ipv4CorrectCsum hdr
                   then csumFin $ csumAdd (B.unpack $ runPut $
                                           ipv4WriteHdr hdr) 0
                   else ipv4Csum hdr
    ipv4WriteHdr hdr {ipv4Csum = sum32}
    putLazyByteString bs


#ifdef HRWS_TEST
{- Unit Tests -}

instance Arbitrary IPv4 where
  arbitrary = do
    v   <- choose (0, 15)
    hl  <- choose (0, 15)
    tos <- arbitrary
    len <- arbitrary
    i   <- arbitrary
    off <- arbitrary
    ttl <- arbitrary
    p   <- arbitrary
    cs  <- arbitrary
    src <- arbitrary
    dst <- arbitrary
    return (IPv4 v hl tos len i off ttl p cs src dst True)

testValidParse :: String -> (IPv4 -> Bool) -> Bool
testValidParse str fn =
  case parse (dummyParsePacket ipv4Decl) "packet parse" str of
       Left  err -> trace (show err) False
       Right val -> fn $ testIPv4Frame val

testIPv4Frame :: Packet -> IPv4
testIPv4Frame (PIPv4 p) = ipv4PktHeader p
testIPv4Frame _ = error "Unexpected packet type"

testIPv4Default :: () -> Bool
testIPv4Default _ = let cmp f = defaultIPv4 == f in
  testValidParse "(ipv4)" cmp

testIPv4Version :: Word8 -> Bool
testIPv4Version v = let cmp f = defaultIPv4 {ipv4Ver = v} == f in
  testValidParse ("(ipv4 ver=" ++ show v ++ ")") cmp

testIPv4Hlen :: Word8 -> Bool
testIPv4Hlen hl = let cmp f = defaultIPv4 {ipv4Hlen = hl} == f in
  testValidParse  ("(ipv4 hlen=" ++ show hl ++ ")") cmp

testIPv4Tos :: Word8 -> Bool
testIPv4Tos t = let cmp f = defaultIPv4 {ipv4Tos = t} == f in
  testValidParse ("(ipv4 tos=" ++ show t ++ ")") cmp

testIPv4Len :: Word16 -> Bool
testIPv4Len l = let cmp f = defaultIPv4 {ipv4Len = l} == f in
  testValidParse ("(ipv4 len=" ++ show l ++ ")") cmp

testIPv4Id :: Word16 -> Bool
testIPv4Id i = let cmp f = defaultIPv4 {ipv4Id =i} == f in
  testValidParse ("(ipv4 id=" ++ show i ++ ")") cmp

testIPv4Offset :: Word16 -> Bool
testIPv4Offset o = let cmp f = defaultIPv4 {ipv4Offset = o} == f in
  testValidParse ("(ipv4 off=" ++ show o ++ ")") cmp

testIPv4Ttl :: Word8 -> Bool
testIPv4Ttl ttl = let cmp f = defaultIPv4 {ipv4Ttl = ttl} == f in
  testValidParse ("(ipv4 ttl=" ++ show ttl ++ ")") cmp

testIPv4Prot :: Word8 -> Bool
testIPv4Prot p = let cmp f = defaultIPv4 {ipv4Prot = p} == f in
  testValidParse ("(ipv4 prot=" ++ show p ++ ")") cmp

testIPv4Csum :: Word16 -> Bool
testIPv4Csum c = let cmp f = defaultIPv4 {ipv4Csum = c} == f in
  testValidParse ("(ipv4 csum=" ++ show c ++ ")") cmp

testIPv4Src :: Word32 -> Bool
testIPv4Src ip = let cmp f = defaultIPv4 {ipv4Src = ip} == f in
  testValidParse ("(ipv4 src=" ++ showIPv4Address ip ++ ")") cmp

testIPv4Dst :: Word32 -> Bool
testIPv4Dst ip = let cmp f = defaultIPv4 {ipv4Dst = ip} == f in
  testValidParse ("(ipv4 dst=" ++ showIPv4Address ip ++ ")") cmp

testIPv4FixCsum :: () -> Bool
testIPv4FixCsum _ = let cmp p = defaultIPv4 {ipv4CorrectCsum = False} == p in
  testValidParse "(ipv4 fixcsum=false)" cmp

testIPv4Pkt0 :: IPv4 -> Bool
testIPv4Pkt0 ip = let cmp p = ip == p in
  testValidParse ("(ipv4 ver=" ++ show (ipv4Ver ip) ++ " hlen=" ++ show (ipv4Hlen ip) ++
                  " tos=" ++ show (ipv4Tos ip) ++ " len=" ++ show (ipv4Len ip) ++
                  " id=" ++ show (ipv4Id ip) ++ " off=" ++ show (ipv4Offset ip) ++
                  " ttl=" ++ show (ipv4Ttl ip) ++ " prot=" ++ show (ipv4Prot ip) ++
                  " csum=" ++ show (ipv4Csum ip) ++ "src=" ++ showIPv4Address (ipv4Src ip) ++
                  " dst=" ++ showIPv4Address (ipv4Dst ip) ++ "fixcsum=true)") cmp

testIPv4Write :: () -> Bool
testIPv4Write _ =
  let expPkt = B.pack [0x45, 0x0c, 0x00, 0x14, 0xbe, 0xef, 0x00, 0x00,
                       0x0a, 0x07, 0xdd, 0x9d, 0x0a, 0x00, 0x00, 0x01,
                       0x0a, 0x00, 0x00, 0x02] in
  let cmp p = runPut (ipv4Write p Nothing B.empty) == expPkt in
  testValidParse "(ipv4 ver=4 hlen=5 tos=12 len=20 id=0xbeef off=0 ttl=0xa prot=7 csum=0xdd9d src=10.0.0.1 dst=10.0.0.2 fixcsum=false)" cmp

testIPv4PseudoWrite :: () -> Bool
testIPv4PseudoWrite _ =
  let expPh = B.pack [0x0a, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x5c] in
  let cmp p = runPut (pseudoIpv4Write p 92) == expPh in
  testValidParse "(ipv4 ver=4 hlen=5 tos=12 len=92 id=0xbeef off=0 ttl=0xa prot=7 csum=0xdd9d src=10.0.0.1 dst=10.0.0.2)" cmp

  
ipv4Tests :: [Test]
ipv4Tests = [
  testProperty "IPv4: Default Packet" testIPv4Default,
  testProperty "IPv4: Version" testIPv4Version,
  testProperty "IPv4: Header Length" testIPv4Hlen,
  testProperty "IPv4: Type of Service" testIPv4Tos,
  testProperty "IPv4: Length" testIPv4Len,
  testProperty "IPv4: ID" testIPv4Id,
  testProperty "IPv4: Offset" testIPv4Offset,
  testProperty "IPv4: TTL" testIPv4Ttl,
  testProperty "IPv4: Protocol" testIPv4Prot,
  testProperty "IPv4: Checksum" testIPv4Csum,
  testProperty "IPv4: Source" testIPv4Src,
  testProperty "IPv4: Dest" testIPv4Dst,
  testProperty "IPv4: Correct Checksum" testIPv4FixCsum,
  testProperty "IPv4: Packet 0" testIPv4Pkt0,
  testProperty "IPv4: Write" testIPv4Write,
  testProperty "IPv4: Write Pseudo Header" testIPv4PseudoWrite
  ]

#endif