packages feed

rws-0.1.1.2: src/Tcp.hs

{-# LANGUAGE CPP #-}
module Tcp where

import Control.Monad
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 Packet
import Lexer
import PseudoSum
import Util

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

finFlag :: Word16
finFlag = 0x0001 :: Word16
synFlag :: Word16
synFlag = 0x0002 :: Word16
rstFlag :: Word16
rstFlag = 0x0004 :: Word16
pshFlag :: Word16
pshFlag = 0x0008 :: Word16
ackFlag :: Word16
ackFlag = 0x0010 :: Word16
urgFlag :: Word16
urgFlag = 0x0020 :: Word16
ecneFlag :: Word16
ecneFlag = 0x0040 :: Word16
cwrFlag :: Word16
cwrFlag = 0x0080 :: Word16

parseTcpFlag :: String -> Word16 -> Parser Word16
parseTcpFlag s f = do
    reserved s
    return f


parseTcpPkt :: Parser Packet -> Parser TcpPkt
parseTcpPkt f = permute
  (tuple <$?> (7, try $ parseIntAttribute "src")
         <|?> (7, parseIntAttribute "dst")
         <|?> (0, try $ parseIntAttribute "seqno")
         <|?> (0, try $ parseIntAttribute "ackno")
         <|?> (0, try $ parseIntAttribute "flags")
         <|?> (0xffff, parseIntAttribute "win")
         <|?> (0, try $ parseIntAttribute "csum")
         <|?> (0, try $ parseIntAttribute "urgno")
         <|?> (20, parseIntAttribute "offset") -- in bytes
         <|?> (0x0, try $ parseTcpFlag "fin" finFlag)
         <|?> (0x0, try $ parseTcpFlag "syn" synFlag)
         <|?> (0x0, parseTcpFlag "rst" rstFlag)
         <|?> (0x0, parseTcpFlag "psh" pshFlag)
         <|?> (0x0, try $ parseTcpFlag "ack" ackFlag)
         <|?> (0x0, try $ parseTcpFlag "urg" urgFlag)
         <|?> (0x0, parseTcpFlag "ecne" ecneFlag)
         <|?> (0x0, try $ parseTcpFlag "cwr" cwrFlag)
         <|?> (True, try $ parseBoolAttribute "fixcsum")
         <|?> (0x0, try $ parseIntAttribute "wsc")
         <|?> (0x0, try $ parseIntAttribute "ts")
         <|?> ([PPayload (Payload 0 0)], parsePayload f))
  where
    tuple s d sq ack fl win c u hl ff sf rf pf af uf ef cf fc wsc ts =
        TcpPkt (Tcp s d sq ack
                 (fl .|. ff .|. sf .|. rf .|. af .|. pf .|. uf .|. ef .|.
                  cf .|. (fromIntegral hl `shiftL` 10))
                 win c u hl fc wsc ts)

tcpDecl :: Parser Packet -> Parser Packet
tcpDecl f = do
 symbol "tcp"
 u <- parseTcpPkt f
 return (PTcp u)

tcpWriteHdr :: Tcp -> Put
tcpWriteHdr p = do
    putWord16be (tcpSrc p)
    putWord16be (tcpDst p)
    putWord32be (tcpSeqNo p)
    putWord32be (tcpAckNo p)
    putWord16be (tcpFlags p)
    putWord16be (tcpWin p)
    putWord16be (tcpCsum p)
    putWord16be (tcpUrg p)
    when (tcpWindowScale p /= 0) $
      do
        putWord8 3; -- Kind = Window Scale
        putWord8 3; -- Len = 3
        putWord8 (tcpWindowScale p); -- Shift Count
        putWord8 0; -- End of Option List

    when (tcpTimeStamp p /= 0) $
       do
         putWord8 8  -- Kind = timestamp
         putWord8 10 -- len = 10
         putWord64be (tcpTimeStamp p)
         putWord8 1  -- no option
         putWord8 0  -- end of option list

tcpWrite :: Tcp -> Maybe Packet -> B.ByteString -> Put
tcpWrite h mp bs = do
  let len = shiftR (tcpFlags h)  12 * 4 + fromIntegral (B.length bs)
  let csum = if tcpCorrectCsum h
                then let sum32 = csumAdd (B.unpack $ runPut $ tcpWriteHdr h) $ genPseudoSum mp len in
                     csumFin $ csumAdd (B.unpack bs) sum32
                else tcpCsum h
  tcpWriteHdr h {tcpCsum = csum}
  putLazyByteString bs

instance PacketWriteable TcpPkt where packetWrite p = tcpWrite (tcpPktHeader p)

#ifdef HRWS_TEST
{- Unit Tests -}


instance Arbitrary Tcp where
  arbitrary = do
    src   <- arbitrary
    dst   <- arbitrary
    sq    <- arbitrary
    ack   <- arbitrary
    flags <- arbitrary
    win   <- arbitrary
    csum  <- arbitrary
    urg   <- arbitrary
    return (Tcp src dst sq ack flags win csum urg 0 True 0 0)

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

testTcpPkt :: Packet -> Tcp
testTcpPkt (PTcp p) = tcpPktHeader p
testTcpPkt _ = error "Unexpected packet type"

testTcpDefault :: () -> Bool
testTcpDefault _ = let cmp f = defaultTcp == f in
  testValidParse "(tcp)" cmp

testTcpPkt0 :: Tcp -> Bool
testTcpPkt0 p = let cmp f = p == f in
  testValidParse ("(tcp offset=0 src=" ++ show (tcpSrc p) ++ " dst=" ++ show (tcpDst p) ++
                  " seqno=" ++ show (tcpSeqNo p) ++ " ackno=" ++ show (tcpAckNo p) ++
                  " flags=" ++ show (tcpFlags p) ++ " win=" ++ show (tcpWin p) ++ " csum=" ++
                  show (tcpCsum p) ++ " urgno=" ++ show (tcpUrg p) ++ ")") cmp

testTcpWrite :: () -> Bool
testTcpWrite _ =
  let expPkt = B.pack [0x04, 0xd2, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a,
                       0x00, 0x00, 0x03, 0xf1, 0x50, 0x11, 0xff, 0xff,
                       0x24, 0x7b, 0x00, 0x00] in
  let cmp p = runPut (tcpWrite p Nothing B.empty) == expPkt in
  testValidParse "(tcp src=1234 fin ack seqno=10 ackno=1009 csum=0x247b fixcsum=false)" cmp


tcpTests :: [Test]
tcpTests = [
  testProperty "TCP: Default Packet" testTcpDefault,
  testProperty "TCP: Packet 0" testTcpPkt0,
  testProperty "TCP: Write" testTcpWrite
  ]

#endif