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)