packages feed

mptcpanalyzer-0.0.2.0: src/MptcpAnalyzer/Types.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module MptcpAnalyzer.Types
-- (
--   -- * types
--   PcapMapping(..)
--   , tcpDest
-- )
where

-- Inspired by Frames/demo/missingData
import MptcpAnalyzer.Stream
import Net.Stream
import Net.Bitset (fromBitMask, toBitMask, ToBitMask)
import Net.IP
import Net.IPv6 (IPv6(..))
import "mptcp-pm" Net.Tcp.Constants (TcpFlag(..))
import Tshark.Fields
import Tshark.TH

import Data.Hashable
import qualified Data.Hashable as Hash
import Data.Monoid (First(..))
import Data.Vinyl (ElField(..), Rec(..), rapply, rmapX, xrec)
import Data.Vinyl.Class.Method
import Data.Vinyl.Functor (Compose(..), (:.))
import qualified Data.Vinyl.TypeLevel as V

import qualified Data.Vinyl as V
import Data.WideWord.Word128
import Data.Word (Word16, Word32, Word64, Word8)
import Frames
import Frames.CSV (ParserOptions(..), QuotingMode(..))
import Frames.ShowCSV
import Frames.TH
-- (Parseable(..), parseIntish, Parsed(..))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector as V
import Frames.ColumnTypeable
import Frames.InCore (VectorFor)
import Language.Haskell.TH
import Numeric (readHex)
import qualified Text.Read as T
-- import GHC.TypeLits
import Control.Lens
import Control.Monad (MonadPlus, liftM, mzero)
import qualified Data.Set as Set
import qualified Data.Text as TS
import qualified Data.Text.Lazy.Builder as B
import Data.Typeable (Typeable)
import qualified Frames as F
import GHC.Generics
import GHC.TypeLits (KnownSymbol)
import MptcpAnalyzer.ArtificialFields
import Options.Applicative
import System.Process (ProcessHandle)


instance ToBitMask TcpFlag

{- Describe a TCP connection, possibly an Mptcp subflow
  The equality implementation ignores several fields
-}
-- data TcpConnection = TcpConnection {
--   -- TODO use libraries to deal with that ? filter from the command line for instance ?
--   srcIp :: IP -- ^Source ip
--   , dstIp :: IP -- ^Destination ip
--   , srcPort :: Word16  -- ^ Source port
--   , dstPort :: Word16  -- ^Destination port
--   , priority :: Maybe Word8 -- ^subflow priority
--   , localId :: Word8  -- ^ Convert to AddressFamily
--   , remoteId :: Word8
--   -- TODO remove could be deduced from srcIp / dstIp ?
--   , subflowInterface :: Maybe Word32 -- ^Interface of Maybe ? why a maybe ?
--   -- add TcpMetrics member
--   -- , tcpMetrics :: Maybe [SockDiagExtension]  -- ^Metrics retrieved from kernel

-- } deriving (Show, Generic, Ord)


declarePrefixedColumns "" baseFields

-- when loading the second pcap to merge, we need to distinguish between the different fields
-- declarePrefixedColumns "test" baseFields
declarePrefixedColumns "" baseFieldsHost2
declarePrefixedColumns "" baseFieldsSender
declarePrefixedColumns "" baseFieldsReceiver

-- todo declare it from ArtificialFields ?
-- artificial types, i.e. created by the app and not tshark
declareColumn "tcpDest" ''ConnectionRole
-- | True if host 1 is sender
declareColumn "senderHost" ''Bool
declareColumn "senderDest" ''ConnectionRole
declareColumn "mptcpDest" ''ConnectionRole
declareColumn "packetHash" ''Int
declareColumn "colOwd" ''Double


-- TODO check it generates
-- HostCols
genRecordFrom "HostCols" baseFields
-- these are useful when merging different
genRecordFromHeaders "" "HostColsPrefixed" baseFieldsHost2
-- genExplicitRecord "test" "HostColsPrefixed" baseFieldsHost2
genRecHashable "HashablePart" baseFields
genRecordFrom "SenderCols" baseFieldsSender
genRecordFrom "ReceiverCols" baseFieldsReceiver

-- | Represent a mapping between 2 pcaps captured at either end of a connection
-- (i.e., one pcap was captured at the client, the other at the receiver)
data PcapMapping a = PcapMapping {
      -- | Host 1 pcap to load
        pmapPcap1 :: FilePath
      , pmapStream1 :: StreamId a
      -- | Host 2
      , pmapPcap2 :: FilePath
      , pmapStream2 :: StreamId a
      -- , pmapVerbose :: Bool
      -- , pmapLimit :: Int -- ^Number of comparisons to show
      -- , pmapMptcp :: Bool -- ^Wether it's an MPTCP
    }


-- row / ManRow
type Packet = Record HostCols
type PacketWithSenderDest = Record (SenderDest ': HostCols)
type PacketWithTcpDest = Record (TcpDest ': HostCols)
-- type PacketWithMptcpDest = Record (MptcpDest ': MptcpDest ': HostCols)

-- https://stackoverflow.com/questions/14020491/is-there-a-way-of-deriving-binary-instances-for-vinyl-record-types-using-derive?rq=1
-- forall t s a rs. (t ~ '(s,a)
-- comparable to Storable
deriving instance  (KnownSymbol s, Hashable a) => Hashable(ElField '(s, a))
deriving instance Hashable TcpFlag

-- | This is only here so we can use hash maps for the grouping step.  This should properly be in Vinyl itself.
instance Hashable (F.Record '[]) where
  hash = const 0
  {-# INLINABLE hash #-}
  hashWithSalt s = const s
  {-# INLINABLE hashWithSalt #-}

instance (V.KnownField t, Hashable (V.Snd t), Hashable (F.Record rs), rs F.⊆ (t ': rs)) => Hashable (F.Record (t ': rs)) where
  hashWithSalt s r = s `Hash.hashWithSalt` F.rgetField @t r `Hash.hashWithSalt` F.rcast @rs r
  {-# INLINABLE hashWithSalt #-}

deriving instance Hashable IP

-- IPv6 is Word128
deriving instance Generic IPv6

deriving instance Hashable Word128
deriving instance Hashable IPv6

-- shadow param
-- @a@ be Tcp / Mptcp
-- @b@ could be the direction
type PcapFrame a = Frame Packet


-- | TODO adapt / rename to AFrame ? AdvancedFrames ?
-- GADT ?
data FrameFiltered a rs = FrameTcp {
    ffCon :: a
    -- StreamConnection b => b
    -- Frame of sthg maybe even bigger with TcpDest / MptcpDest
    , ffFrame :: Frame rs
  } deriving Functor


aframeLength :: FrameFiltered a rs -> Int
aframeLength = frameLength . ffFrame


-- Helper to pass information across functions
data MyState = MyState {
    _stateCacheFolder :: FilePath
  , _loadedFile   :: Maybe (FrameRec HostCols)  -- ^ cached loaded pcap
  , _prompt   :: String  -- ^ Prompt entry
  , _sharkdHandle   :: Maybe ProcessHandle -- ^ sharkd process handle
}

makeLenses ''MyState



type OptionList = T.Text


-- Used to parse tokens
instance (Read a, Typeable a, Frames.ColumnTypeable.Parseable a) => Frames.ColumnTypeable.Parseable (Maybe a) where
  parse txt = case T.null txt of
    True -> return $ Definitely Nothing
    False -> do
      val2 <- val
      return $ case val2 of
        Possibly x -> Possibly (Just x)
        Definitely x -> Definitely (Just x)
    where
      val :: MonadPlus m => m (Parsed a)
      val = parse txt

      -- val2 :: MonadPlus m => m (Parsed (Maybe a))
      -- val2 = Just <$> val
    -- case w64 of
    --   Left msg -> error $ "could not read " ++ show txt ++ ", error: " ++ msg
    --   Right val -> Definitely (Just val)
    -- where
    --     w64 = T.readEither (T.unpack txt)


-- TODO parse based on ,
-- instance Frames.ColumnTypeable.Parseable (Maybe OptionList) where
--   parse _ = return $ Definitely Nothing

instance Frames.ColumnTypeable.Parseable Word16 where
  parse = parseIntish
instance Frames.ColumnTypeable.Parseable Word32 where
  parse = parseIntish

instance Frames.ColumnTypeable.Parseable Word64 where
  parse = parseIntish

instance Frames.ColumnTypeable.Parseable IP where
  -- parse :: MonadPlus m => T.Text -> m (Parsed a)
-- IP.decode :: Text -> Maybe IP
  -- fmap Definitely
  parse text = case decode text of
    Nothing -> return $ Possibly $ ipv4 0 0 0 0
    Just ip -> return $ Definitely ip

-- instance Frames.ColumnTypeable.Parseable Word64 where
--   parse = parseIntish

instance Readable (StreamId a) where
  fromText t = case T.readMaybe (T.unpack t) of
      Just streamId -> return $ StreamId streamId
      Nothing -> mzero


instance Frames.ColumnTypeable.Parseable (StreamId Mptcp) where
  parse = parseIntish

instance Frames.ColumnTypeable.Parseable (StreamId Tcp) where
  parse = parseIntish


--
parseList :: (MonadPlus m, Typeable a, Frames.ColumnTypeable.Parseable a) => Text -> m (Parsed [a])
parseList text = fmap Definitely (mapM parse' (T.splitOn "," text))

instance Frames.ColumnTypeable.Parseable [Word64] where
  -- expected type parse :: MonadPlus m => T.Text -> m (Parsed [a])
  parse = parseList


-- could not parse 0x00000002
-- strip leading 0x
instance Frames.ColumnTypeable.Parseable [TcpFlag] where
  parse text = case readHex (T.unpack $ T.drop 2 text) of
    -- TODO generate
    [(n, "")] -> return $ Definitely $ fromBitMask n
    _ -> error $ "TcpFlags: could not parse " ++ T.unpack text

-- TODO rewrite it as wireshark exposes it, eg, in hexa ?
-- instance ShowCSV [TcpFlag] where
--   -- showCSV :: a -> Text
--   showCSV flagList = T.concat texts
--     where
--       texts = map (T.pack . show .fromEnum) flagList
--       res = toBitMask flagList

instance ShowCSV [TcpFlag] where
  -- showCSV :: a -> Text
  showCSV flagList = T.concat texts
    where
      texts = map (T.pack . show .fromEnum) flagList
      res = toBitMask flagList

instance ShowCSV [Word64] where
  -- showCSV :: a -> Text
  showCSV seqs = T.intercalate "," texts
    where
      texts = map (T.pack . show .fromEnum) seqs

instance ShowCSV IP where
  showCSV = encode

instance ShowCSV Word8 where
instance ShowCSV Word16 where
instance ShowCSV Word32 where
instance ShowCSV Word64 where
instance ShowCSV m => ShowCSV (Maybe m) where
  showCSV = \case
    Nothing -> ""
    Just x -> showCSV x

--
instance ShowCSV (StreamId a) where
  showCSV (StreamId stream) = showCSV stream


-- type ManMaybe = Rec (Maybe :. ElField) ManColumns
-- TODO goal here is to choose the most performant Data.Vector
type instance VectorFor Word8  = V.Vector
type instance VectorFor Word16 = V.Vector
type instance VectorFor Word32 = V.Vector
type instance VectorFor Word64 = V.Vector
type instance VectorFor (Maybe Word8) = V.Vector
type instance VectorFor (Maybe Word16) = V.Vector
type instance VectorFor (Maybe Word32) = V.Vector
type instance VectorFor (Maybe Word64) = V.Vector
type instance VectorFor IP = V.Vector
type instance VectorFor TcpFlagList = V.Vector
type instance VectorFor (StreamId a) = V.Vector

type instance VectorFor (Maybe Int) = V.Vector
type instance VectorFor (Maybe Bool) = V.Vector
type instance VectorFor (Maybe OptionList) = V.Vector
type instance VectorFor MbMptcpStream = V.Vector
type instance VectorFor ConnectionRole = V.Vector
-- FIX generalize
type instance VectorFor [Word64] = V.Vector
type instance VectorFor (Maybe [Word64]) = V.Vector


type TcpFields rs = (TcpSrcPort ∈ rs, TcpDestPort ∈ rs, TcpStream ∈ rs)
type IpFields rs = (IpSource ∈ rs, IpDest ∈ rs)