packages feed

rws-0.1.1.2: src/Ethernet.hs

{-# LANGUAGE CPP #-}
module Ethernet where

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

import Packet
import Lexer
import Util

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

macAddressPart :: Parser Word8
macAddressPart = do
  h1 <- hexDigit
  h2 <- hexDigit
  char ':'
  let w = digitToInt h1 `shiftL` 4 .|. digitToInt h2
  return (fromIntegral w)

macAddressPart' :: Parser Word8
macAddressPart' = do
  h1 <- hexDigit
  h2 <- hexDigit
  let w = digitToInt h1 `shiftL` 4 .|. digitToInt h2
  return (fromIntegral w)

macAddressDecl :: Parser MacAddress
macAddressDecl = do
  h0 <- macAddressPart
  h1 <- macAddressPart
  h2 <- macAddressPart
  h3 <- macAddressPart
  h4 <- macAddressPart
  h5 <- macAddressPart'
  return (MacAddress h0 h1 h2 h3 h4 h5)

parseMacAddress :: String -> Parser MacAddress
parseMacAddress s = do
  symbol s
  symbol "="
  a <- macAddressDecl
  whiteSpace
  return a

parseEthFrame :: Parser Packet -> Parser EthernetFrame
parseEthFrame f = permute
  (tuple <$?> (MacAddress 0 0 0 0 0 1, parseMacAddress "dst" )
         <|?> (MacAddress 0 0 0 0 0 2, parseMacAddress "src" )
         <|?> (0x800, parseIntAttribute "type")
         <|?> ([PPayload defaultPayload], parsePayload f))
  where
    tuple a b c = EthernetFrame (Ethernet a b c)

ethDecl :: Parser Packet -> Parser Packet
ethDecl f = do
 symbol "eth"
 ef <- parseEthFrame f
 return $ PEth ef

macAddressWrite :: MacAddress -> Put
macAddressWrite a = do
    putWord8 (macAddress0 a)
    putWord8 (macAddress1 a)
    putWord8 (macAddress2 a)
    putWord8 (macAddress3 a)
    putWord8 (macAddress4 a)
    putWord8 (macAddress5 a)

ethernetWriteHdr :: Ethernet -> Put
ethernetWriteHdr f = do
    macAddressWrite (ethernetDst f)
    macAddressWrite (ethernetSrc f)
    putWord16be (ethernetType f)

ethernetWrite :: Ethernet -> Maybe Packet -> B.ByteString -> Put
ethernetWrite h _ bs = do
    ethernetWriteHdr h
    putLazyByteString bs

instance PacketWriteable EthernetFrame where packetWrite f = ethernetWrite (ethernFrameHeader f)

#ifdef HRWS_TEST
{- Unit Tests -}

instance Arbitrary MacAddress where
  arbitrary = do
    w0 <- arbitrary
    w1 <- arbitrary
    w2 <- arbitrary
    w3 <- arbitrary
    w4 <- arbitrary
    w5 <- arbitrary
    return (MacAddress w0 w1 w2 w3 w4 w5)

instance Arbitrary Ethernet where
  arbitrary = do
    dst <- arbitrary
    src <- arbitrary
    t <- arbitrary
    return (Ethernet dst src t)

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

testInvalidParse :: String -> Bool
testInvalidParse str =
  case parse (dummyParsePacket ethDecl) "packet parse" str of
       Left  _ -> True
       Right _ -> False

testEthDefault :: () -> Bool
testEthDefault _ = let cmp f = defaultEthernet == f in
  testValidParse "(eth)" cmp

testEthFrame :: Packet -> Ethernet
testEthFrame (PEth f) = ethernFrameHeader f
testEthFrame _ = error "Unexpected packet type"

testEthType :: Word16 -> Bool
testEthType t = let cmp f = defaultEthernet {ethernetType = t} == f in
  testValidParse ("(eth type=" ++ show t ++ ")") cmp

testEthDst :: MacAddress -> Bool
testEthDst a =
  let cmp f = defaultEthernet {ethernetDst = a} == f in
  testValidParse ("(eth dst=" ++ show a ++ ")") cmp

testEthSrc :: MacAddress -> Bool
testEthSrc a =
  let cmp f = defaultEthernet {ethernetSrc = a} == f in
  testValidParse ("(eth src=" ++ show a ++ ")") cmp

testEthFrame0 :: Ethernet -> Bool
testEthFrame0 f =
  let cmp a = f == a in
  testValidParse ("(eth dst=" ++ show (ethernetDst f) ++ " src=" ++ show (ethernetSrc f) ++
                  " type=" ++ show (ethernetType f) ++ ")") cmp

testEthInvFrame0 :: () -> Bool
testEthInvFrame0 _ = testInvalidParse "(eth src=asdf)"

testEthInvFrame1 :: () -> Bool
testEthInvFrame1 _ = testInvalidParse "(eth dst=1:2:2 )"

testEthInvFrame2 :: () -> Bool
testEthInvFrame2 _ = testInvalidParse "(eth type=src=banan )"

testEthWrite :: () -> Bool
testEthWrite _ =
  let expFrame = B.pack [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, -- dst
                         0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, -- src
                         0xca, 0xfe] in                      -- type 
  let cmp f = runPut (ethernetWrite f Nothing B.empty) == expFrame in
  testValidParse "(eth dst=00:11:22:33:44:55 src=66:77:88:99:aa:bb type=0xcafe)" cmp

ethTests :: [Test]
ethTests = [
  testProperty "Ethernet: Default Packet" testEthDefault,
  testProperty "Ethernet: Type" testEthType,
  testProperty "Ethernet: Destination" testEthDst,
  testProperty "Ethernet: Source" testEthSrc,
  testProperty "Ethernet: valid frame" testEthFrame0,
  testProperty "Ethernet: invalid frame 0" testEthInvFrame0,
  testProperty "Ethernet: invalid frame 1" testEthInvFrame1,
  testProperty "Ethernet: invalid frame 2" testEthInvFrame2,
  testProperty "Ethernet: Write" testEthWrite
  ]

#endif