packages feed

mptcpanalyzer-0.0.2.0: src/MptcpAnalyzer/Pcap.hs

{-|
Module: MptcpAnalyzer.Pcap
Maintainer  : matt
License     : GPL-3

Pot-pourri
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE PackageImports #-}
module MptcpAnalyzer.Pcap (
    addTcpDestToFrame
    , addMptcpDestToFrame
    , addMptcpDest
    , addTcpDestinationsToAFrame
    , buildTcpConnectionFromStreamId
    , buildMptcpConnectionFromStreamId
    , defaultParserOptions
    , genTcpDestFrame
    , genTcpDestFrameFromAFrame
    , exportToCsv
    , loadRows
    , getTcpStreams
    , getMptcpStreams
    , buildSubflowFromRecord
    , buildSubflowFromTcpStreamId
    , buildTcpConnectionFromRecord
    , buildTcpConnectionTupleFromRecord

    , genMptcpEndpointConfigFromRow 
    -- TODO remove ? use instance instead
    , showMptcpSubflowText
    , StreamConnection(..)
    , showConnection
    , scoreTcpCon
    , scoreMptcpCon
    -- , showMptcpSubflowText
    )
where


import MptcpAnalyzer.ArtificialFields
import MptcpAnalyzer.Stream
import MptcpAnalyzer.Types
import MptcpAnalyzer.Utils.Text
import Net.Mptcp.Connection
import Net.Tcp
import Net.Stream
import Net.Tcp.Constants (TcpFlag(..))
import Tshark.Fields
import Tshark.TH

-- hackage
import Control.Lens ((^.))
import Data.Kind (Type)
import Data.Monoid (First(..))
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Vector as V
import Frames
import Frames.CSV
       ( ParserOptions(..)
       , QuotingMode(..)
       , ReadRec
       , pipeTableEitherOpt
       , produceTextLines
       , readFileLatin1Ln
       , readTableMaybeOpt
       )
import Frames.Col
import Frames.ColumnTypeable (Parseable(..), Parsed(..), parseIntish)
import Frames.ShowCSV
import Frames.TH
import System.Exit
import System.IO
       ( BufferMode(LineBuffering)
       , Handle
       , SeekMode(AbsoluteSeek)
       , hGetContents
       , hSeek
       , hSetBuffering
       )
import System.Process
-- for Record
-- import Frames.Rec (Record(..))
import Data.List (intercalate)
import Net.IP
-- for symbol
-- import GHC.Types
import qualified Control.Foldl as L
import qualified Data.Set as Set
-- import Language.Haskell.TH
-- import Language.Haskell.TH.Syntax
-- import Lens.Micro
-- import Lens.Micro.Extras
import Control.Lens
import qualified Data.Foldable as F
import Data.Maybe (catMaybes, fromJust, isNothing)
import Data.Vinyl (ElField(..), Rec(..), rapply, rmapX, xrec)
import Data.Vinyl.Class.Method
import Data.Vinyl.Functor (Compose(..), (:.))
import Data.Word (Word16, Word32, Word64, Word8)
import GHC.Base (Symbol)
import GHC.List (foldl')
import GHC.TypeLits (KnownSymbol)
import Numeric (readHex)
import Pipes (Producer, cat, (>->))
import qualified Pipes.Prelude as P
-- import qualified Frames.InCore
import Control.Exception (assert)
import Data.Either (lefts, rights)
import qualified Data.Map as Map
import Debug.Trace
import qualified Frames.InCore as I
import GHC.IO.Handle (hClose)
import System.Environment (getEnvironment)
import System.IO.Temp
import Tshark.Main

-- tableTypes is a Template Haskell function, which means that it is executed at compile time. It generates a data type for our CSV, so we have everything under control with our types.


-- on veut la generer
-- [[t|Ident Int|], [t|Happiness|]]
-- tableTypesExplicit' :: [Q Type] -> RowGen a -> FilePath -> DecsQ
-- tableTypesExplicit'

-- tableTypesExplicit'
--   (getTypes baseFields)
--   -- [ Field Word64 ]
--   -- [[t| Word64|]]
--   ((rowGen "data/test-1col.csv")
--   { rowTypeName = "Packet"
--         , separator = ","
--         -- TODO I could generate it as well
--         -- , columnNames
--     })
--     -- path
--     "data/test-simple.csv"



-- shadow type to know if it was filtered or not
-- Make it a record ?
-- first argument allows to override csv header ("headerOverride")
defaultParserOptions :: ParserOptions
defaultParserOptions = ParserOptions Nothing (T.pack [csvDelimiter defaultTsharkPrefs]) NoQuoting

-- nub => remove duplicates
-- or just get the column
getTcpStreams :: FrameRec HostCols -> [StreamIdTcp]
getTcpStreams ps = L.fold L.nub (view tcpStream <$> ps)

-- | to list
getMptcpStreams :: FrameRec HostCols -> [StreamId Mptcp]
getMptcpStreams ps = L.fold L.nub $ catMaybes $ F.toList (view mptcpStream <$> ps)
-- filterFrame  (\x -> x ^. mptcpStream == Just streamId) frame



{- Export to CSV
sets WIRESHARK_CONFIG_DIR so that the user profile doesn't influence the output
-}
exportToCsv ::
  TsharkParams
  -> FilePath  -- ^Path to the pcap
  -> Handle -- ^ temporary file
-- ^See haskell:readCreateProcessWithExitCode
  -> IO (ExitCode, String)
exportToCsv params pcapPath tmpFileHandle = do
    curEnv <- getEnvironment
    withSystemTempFile "tshark-profile" $ \tempDir _ -> do
      let
          (RawCommand bin args) = generateCsvCommand fields (Right pcapPath) (params )
          createProc :: CreateProcess
          createProc = (proc bin args) {
              std_err = CreatePipe,
              std_out = UseHandle tmpFileHandle,
              env = Just $ curEnv ++ [ ("WIRESHARK_CONFIG_DIR", tempDir) ],
              delegate_ctlc = True
              }
      putStrLn $ "Exporting fields " ++ show fields
      putStrLn $ "Command run: " ++ show (RawCommand bin args)
      -- TODO redirect stdout towards the out handle
      hSetBuffering tmpFileHandle LineBuffering
      hSeek tmpFileHandle AbsoluteSeek 0 >> T.hPutStrLn tmpFileHandle fieldHeader
      (_, _, Just herr, ph) <-  createProcess_ "error" createProc
      exitCode <- waitForProcess ph
      -- TODO do it only in case of error ?
      err <- hGetContents herr
      hClose herr
      return (exitCode, err)
    where
      fields :: [T.Text]
      fields = Map.elems $ Map.map tfieldFullname baseFields

      csvSeparator = T.pack [csvDelimiter params]
      fieldHeader :: Text
      fieldHeader = T.intercalate csvSeparator (Map.keys baseFields)


loadRows :: (I.RecVec a, ReadRec a) => FilePath -> IO (FrameRec a)
loadRows path = inCoreAoS (
  eitherProcessed path
  )


type ManEither = Rec (Either T.Text :. ElField) (RecordColumns Packet)

-- pipteTable will tokenize on its own
-- loadRowsEither :: MonadSafe m => FilePath -> Producer ManEither m ()
-- loadRowsEither path =  produceTextLines path >-> pipeTableEitherOpt defaultParserOptions

{- |Load rows and errors when it can't load a specific line
-}
eitherProcessed :: (ReadRec a, MonadSafe m) => FilePath -> Producer (Record a) m ()
eitherProcessed path = produceTextLines path
  >-> pipeTableEitherOpt defaultParserOptions >-> P.map fromEither
  where
    -- fromEither :: Rec (Either Text :. ElField) (RecordColumns Packet) -> Packet
    fromEither x = case recEither x of
      Left _txt -> error ( "eitherProcessed failure : " ++ T.unpack _txt)
      Right pkt -> pkt

    recEither = rtraverse getCompose

-- | Undistribute 'Maybe' from a 'Rec' 'Maybe'. This is just a
-- specific usage of 'rtraverse', but it is quite common.
-- recEither :: Rec (Either Text :. ElField) cs -> Either Text (Record cs)
-- recEither = rtraverse getCompose

-- data TsharkPrefs = TsharkPrefs {
--     analyzeTcpSeq :: Bool
--     , analyzeMptcp :: Bool
--     , mptcpRelSeq :: Bool
--     , analyzeMptcp :: Bool
--   } deriving Show

{-
-}
getTcpFrame :: FrameRec HostCols -> StreamId Tcp -> Either String (FrameFiltered TcpConnection Packet)
getTcpFrame = buildTcpConnectionFromStreamId

-- | For now assume the packet is the first syn from client to server
-- TODO this is wrong, assumes source ip is client, convert to return a TcpConnectionOriented
buildTcpConnectionFromRecord :: (
  IpFields rs, TcpSrcPort ∈ rs, TcpDestPort ∈ rs, TcpStream ∈ rs
  ) => Record rs -> TcpConnection
buildTcpConnectionFromRecord r =
  TcpConnection {
      conTcpClientIp = r ^. ipSource
    , conTcpServerIp = r ^. ipDest
    , conTcpClientPort = r ^. tcpSrcPort
    , conTcpServerPort = r ^. tcpDestPort
    , conTcpStreamId = r ^. tcpStream
  }

buildTcpConnectionTupleFromRecord :: (
  IpFields rs, TcpSrcPort ∈ rs, TcpDestPort ∈ rs, TcpStream ∈ rs
  ) => Record rs -> TcpConnectionOriented
buildTcpConnectionTupleFromRecord r =
  TcpConnectionOriented {
      conTcpSourceIp = r ^. ipSource
    , conTcpDestinationIp = r ^. ipDest
    , conTcpSourcePort = r ^. tcpSrcPort
    , conTcpDestinationPort = r ^. tcpDestPort
    -- , conTcpStreamId = r ^. tcpStream
  }

{- Builds a Tcp connection from a non filtered frame
-}
buildTcpConnectionFromStreamId ::
  FrameRec HostCols
  -> StreamId Tcp -> Either String (FrameFiltered TcpConnection Packet)
buildTcpConnectionFromStreamId frame streamId =
    if frameLength synPackets < 1 then
      Left $ "No packet with any SYN flag for tcp.stream " ++ show streamId
    else
      -- TODO check who is client
      Right $ FrameTcp (buildTcpConnectionFromRecord $ frameRow synPackets 0) streamPackets
    where
      streamPackets = filterFrame  (\x -> x ^. tcpStream == streamId) frame
      synPackets = filterFrame (\x -> TcpFlagSyn `elem` (x ^. tcpFlags)) streamPackets


-- |
-- buildMasterSubflowFromRecord ::


-- | Builds
-- should expect a filteredFrame with MPTCP
-- buildSubflowFromTcpStreamId :: FrameFiltered TcpConnection Packet -> StreamId Tcp -> Either String (FrameFiltered MptcpSubflow Packet)
buildSubflowFromRecord :: Packet -> MptcpSubflow
buildSubflowFromRecord row =
  MptcpSubflow {
          sfConn = sfCon
        -- TODO ignore if it's master token
        , sfJoinToken = row ^. mptcpRecvToken
        , sfPriority = Nothing
        -- TODO
        , sfLocalId = 0
        , sfRemoteId = 0
        -- todo load it from row
        , sfInterface = Nothing
      }
  where
      sfCon = buildTcpConnectionFromRecord row


buildSubflowFromTcpStreamId ::
  (
  rs ⊆ HostCols
  , I.RecVec rs
  , TcpFlags ∈ rs , TcpStream ∈ rs, MptcpRecvToken ∈ rs
  , IpSource ∈ rs, IpDest ∈ rs, TcpSrcPort ∈ rs, TcpDestPort ∈ rs, TcpStream ∈ rs
  )
  => FrameRec rs
  -> StreamId Tcp
  -> Either String (FrameFiltered MptcpSubflow (Record rs))
buildSubflowFromTcpStreamId frame streamId =
    if frameLength synPackets < 1 then
      Left $ "No packet with any SYN flag for tcp.stream " ++ show streamId
    else
      -- TODO check who is client
      Right $ FrameTcp sf streamPackets
    where
      syn0 = frameRow synPackets 0
      streamPackets = filterFrame  (\x -> x ^. tcpStream == streamId) frame
      synPackets = filterFrame (\x -> TcpFlagSyn `elem` (x ^. tcpFlags)) streamPackets
      sfCon = buildTcpConnectionFromRecord syn0
      -- rcvToken
      sf = MptcpSubflow {
        sfConn = sfCon
        -- TODO ignore if it's master token
        , sfJoinToken = syn0 ^. mptcpRecvToken
        , sfPriority = Nothing
        , sfLocalId = 0
        , sfRemoteId = 0
        , sfInterface = Nothing
      }

-- | Sets mptcp role column
-- TODO maybe je devrais juste generer un
addMptcpDest ::
    (
      -- Frames.InCore.RecVec rs,
      -- HostCols ⊆ rs
      -- MptcpStream ∈ rs, TcpStream  ∈ rs, IpSource ∈ rs, IpDest ∈ rs, TcpSrcPort ∈ rs, TcpDestPort ∈ rs
      -- rs = HostCols
      ) =>
      Frame (Record HostCols)
      -> MptcpConnection
      -> FrameRec  (
            MptcpDest ': TcpDest ': HostCols
          )
addMptcpDest frame con =
    -- foldl' (\tframe sf -> addDestToFrame tframe sf) startingFrame subflows
    mconcat subflowFrames
    where
      -- filteredFrame = filterFrame  (\x -> x ^. mptcpStream == Just (mpconStreamId con)) frame
      -- filteredFrame = filterFrame  (\x -> (rgetField @MptcpStream x) == Just (mpconStreamId con)) frame

      subflowFrames = map addDestsToSubflowFrames subflows

      addDestsToSubflowFrames sf = addMptcpDestToFrame' (addTcpDestToFrame frame (sfConn sf)) sf

      addMptcpDest' role x = Col role :& x

      addMptcpDestToFrame' frame' sf = fmap (addMptcpDest' (getMptcpDest con sf)) frame'

      startingFrame = fmap setTempDests frame
      setTempDests :: Record rs -> Record ( MptcpDest ': TcpDest ': rs)
      setTempDests x = Col RoleClient :& Col RoleClient :& x
      addMptcpDestToRec x role = Col role :& x
      subflows = Set.toList $ _mpconSubflows con

addMptcpDestToFrame :: MptcpConnection -> FrameFiltered MptcpSubflow Packet -> FrameRec '[MptcpDest]
addMptcpDestToFrame mpcon (FrameTcp sf frame) = fmap (addMptcpDest' (getMptcpDest mpcon sf)) frame
  where
      addMptcpDest' role x = Col role :& RNil


getMptcpDest :: MptcpConnection -> MptcpSubflow -> ConnectionRole
getMptcpDest mptcpCon sf = case sfJoinToken sf of
  -- master subflow, dest is by definition the server
  Nothing -> RoleServer
  Just token -> if token == (_mecToken . _mpconServerConfig) mptcpCon then
    RoleServer
  else
    RoleClient


-- | Sets TCP role column
-- append a column with a value role
-- Todo accept a 'FrameFiltered'
-- I want to check it is included
-- TODO add an unsafe version ?
addTcpDestToFrame :: (
  I.RecVec rs
  ,IpSource ∈ rs, IpDest ∈ rs
  , IpDest ∈ rs, TcpSrcPort ∈ rs, TcpDestPort ∈ rs
  , TcpStream ∈ rs
  )
    => FrameRec rs
    -> TcpConnection
    -> FrameRec ( TcpDest ': rs )
addTcpDestToFrame frame con = do
  assert
    -- check that they all belong to the same stream
    (length ( L.fold L.nub (view tcpStream <$> frame)) == 1)
    fmap (\x -> addTcpDestToRec x (computeTcpDest x con)) streamFrame
  where
    streamFrame = frame


-- | Generates a frame with a single column containing the TcpDest
genTcpDestFrame :: (
  I.RecVec rs
  , IpSource ∈ rs, IpDest ∈ rs
  , TcpSrcPort ∈ rs, TcpDestPort ∈ rs
  , TcpStream ∈ rs
  )
    => FrameRec rs
    -> TcpConnection
    -> FrameRec '[TcpDest]
genTcpDestFrame frame con = fmap (\x -> Col (computeTcpDest x con) :& RNil) streamFrame
    where
      streamFrame = filterFrame  (\x -> rgetField @TcpStream x == conTcpStreamId con) frame

genTcpDestFrameFromAFrame :: (
  I.RecVec rs
  , IpSource ∈ rs, IpDest ∈ rs
  , TcpSrcPort ∈ rs, TcpDestPort ∈ rs
  , TcpStream ∈ rs
  )
    => FrameFiltered TcpConnection (Record rs)
    -> FrameRec '[TcpDest]
genTcpDestFrameFromAFrame aframe = genTcpDestFrame (ffFrame aframe) (ffCon aframe)


computeTcpDest :: (
  TcpStream ∈ rs
  , IpFields rs
  , TcpSrcPort ∈ rs
  , TcpDestPort ∈ rs
  ) => Record rs
  -> TcpConnection -> ConnectionRole
computeTcpDest x con  = if rgetField @IpSource x == conTcpClientIp con
                && rgetField @IpDest x == conTcpServerIp con
                && rgetField @TcpSrcPort x == conTcpClientPort con
                && rgetField @TcpDestPort x == conTcpServerPort con
                && rgetField @TcpDestPort x == conTcpServerPort con
                -- TODO should error if not the same streamId
                -- && (rgetField @TcpStream x) == (conTcpStreamId con)
        then RoleServer else RoleClient


-- | TODO
-- See @addTcpDestToFrame@
addTcpDestinationsToAFrame :: (
  -- HostCols ⊆ rs,
  I.RecVec rs
  -- , HostCols <: rs
  -- , HostCols ∈ rs
  , IpFields rs
  , TcpFields rs)
  => FrameFiltered TcpConnection (Record rs)
  -> FrameFiltered TcpConnection (Record (TcpDest ': rs))
addTcpDestinationsToAFrame aframe =
  aframe { ffFrame = addDestinationsToFrame' (ffCon aframe)}
  where
    frame = ffFrame aframe
    addDestinationsToFrame' con = addTcpDestToFrame frame con

-- append a field with a value role
addTcpDestToRec :: (TcpStream ∈ rs, IpSource ∈ rs, IpDest ∈ rs, TcpSrcPort ∈ rs, TcpDestPort ∈ rs)
  => Record rs -> ConnectionRole ->  Record  ( TcpDest ': rs )
addTcpDestToRec x role = (Col role) :& x


-- TODO take into account the different mptcp versions ?
genMptcpEndpointConfigFromRow :: Packet -> Maybe MptcpEndpointConfiguration
genMptcpEndpointConfigFromRow synAckPacket =
  case (synAckPacket ^. mptcpSendKey, synAckPacket ^. mptcpExpectedToken, synAckPacket ^. mptcpVersion) of 
    (Just key, Just token, Just version) -> Just $ MptcpEndpointConfiguration key token version
    _ -> Nothing
    -- error $ "Could not find key/token/version " ++ show synAckPacket

-- retreiveMptcpServerTokenFromRow :: Packet -> Maybe (Word64, Word32)
-- retreiveMptcpServerTokenFromRow synAckPacket = 
--   case (synAckPacket ^. mptcpSendKey, synAckPacket ^. mptcpExpectedToken) of 
--     (Just key, Just token) -> Just (key, token)
--     _ -> error "Could not generate"

-- TODO

buildMptcpConnectionFromStreamId :: FrameRec HostCols
    -> StreamId Mptcp -> Either String (FrameFiltered MptcpConnection Packet)
buildMptcpConnectionFromStreamId frame streamId = do
    -- Right $ frameLength synPackets
    if frameLength streamPackets < 1 then
      Left $ "No packet with mptcp.stream == " ++ show streamId
    else if frameLength synAckPackets < 1 then
      Left $ "No syn/ack packet found for stream" ++ show streamId ++ " First packet: "
      -- ++ show streamPackets
    else if lefts subflows /= [] then
      Left $ concat (lefts subflows)
    else if mbServerConfig == Nothing then
      Left $ "Could not find MPTCP server config in " ++ show synAckPacket
    else

      case buildTcpConnectionFromStreamId streamPackets (synPacket ^. tcpStream) of
        Left err -> Left err
        Right aframe -> let
            clientFrame = filterFrame (\x -> ((not . isNothing) (x ^. mptcpSendKey))) (ffFrame aframe)
            mbClientConfig = genMptcpEndpointConfigFromRow (frameRow clientFrame 0)
          in
            if frameLength clientFrame == 0 then
              Left $ "Could not find mptcp client key"
            else
              -- TODO now add a check on abstime
              -- if ds.loc[server_id, "abstime"] < ds.loc[client_id, "abstime"]:
              --     log.error("Clocks are not synchronized correctly")
              -- update temporary fframe with the computed subflows
              Right $ FrameTcp {
                    ffCon = tempMptcpConn mbClientConfig
                  , ffFrame = streamPackets
                }
    where
      streamPackets :: FrameRec HostCols
      streamPackets = filterFrame  (\x -> x ^. mptcpStream == Just streamId) frame
      --
      -- |Just for the time
      tempMptcpConn clientConfig = MptcpConnection {
            mpconStreamId = streamId
          -- kinda risky, assumes we have the server key always
          , _mpconServerConfig = fromJust mbServerConfig
          , _mpconClientConfig = fromJust clientConfig
          -- , mptcpNegotiatedVersion = fromIntegral $ fromJust clientMptcpVersion :: Word8

          , _mpconSubflows = Set.fromList $ map ffCon (rights subflows)
        }
      -- suppose tcpflags is a list of flags, check if it is in the list
      -- of type FrameRec [(Symbol, *)]
      -- Looking for synack packets
      synPackets = filterFrame (\x -> TcpFlagSyn `elem` (x ^. tcpFlags)) streamPackets
      synAckPackets = filterFrame (\x -> TcpFlagSyn `elem` (x ^. tcpFlags) && TcpFlagAck `elem` (x ^. tcpFlags)) streamPackets


      synPacket = frameRow synPackets 0
      synAckPacket = frameRow synAckPackets 0
      mbServerConfig = genMptcpEndpointConfigFromRow synAckPacket
      -- clientConfig :: Maybe MptcpEndpointConfiguration
      -- clientConfig = case mbServerConfig of
      --   Nothing -> Nothing
      --   Just serverConfig ->
      --     if mecVersion == 0 then genMptcpEndpointConfigFromRow synPacket
      --     else genMptcpEndpointConfigFromRow

      masterTcpstreamId = synPacket ^. tcpStream
      clientMptcpVersion = synPacket ^. mptcpVersion

      --
      subflows = map (buildSubflowFromTcpStreamId frame) (getTcpStreams streamPackets)


-- filterFrame / buildFrameFromStreamId
{- Common interface to work with TCP and MPTCP connections
-}
class StreamConnection a b | a -> b where
  -- | How
  -- type ConnectionType :: Type
  showConnectionText :: a -> Text
  -- describeConnection :: a -> Text
  buildFrameFromStreamId :: Frame Packet -> StreamId b -> Either String (FrameFiltered a Packet)
  -- type Needs a :: Constraint

  -- type toto = Int

  -- | Compare two conection and give a similarityScore
  similarityScore :: a -> a -> Int
  -- listConnections :: FrameFiltered () [a]
  -- summarize :: a -> Text
  -- GetLabel ?


-- | Compares 2 TCP connections and gives a score
-- The higher the score, the more similar the 2 connections are.
scoreTcpCon :: TcpConnection -> TcpConnection -> Int
scoreTcpCon con1 con2 =
  -- If every parameter is equal, returns +oo else 0
  -- TODO also match on isn in case ports got reused

  foldl (\acc toAdd -> acc + 10 * fromEnum toAdd) (0 :: Int) [
    conTcpClientIp con1 == conTcpClientIp con2
    , conTcpClientPort con1 == conTcpClientPort con2
    , conTcpServerIp con1 == conTcpServerIp con2
    , conTcpServerPort con1 == conTcpServerPort con2
  ]


instance StreamConnection TcpConnection Tcp where
  showConnectionText = showTcpConnectionText
  buildFrameFromStreamId = buildTcpConnectionFromStreamId
  similarityScore = scoreTcpCon


-- | Computes a score
scoreMptcpCon :: MptcpConnection -> MptcpConnection -> Int
scoreMptcpCon con1 con2 =
  let keyScore = if con1 ^. mpconServerConfig ^. mecKey == con2 ^. mpconServerConfig ^. mecKey
                    && con1 ^. mpconClientConfig ^. mecKey == con2 ^. mpconClientConfig ^. mecKey
      then 200
      else 0
  in
    keyScore


instance StreamConnection MptcpConnection Mptcp where
  showConnectionText = showMptcpConnectionText
  buildFrameFromStreamId = buildMptcpConnectionFromStreamId
  similarityScore = scoreMptcpCon

instance StreamConnection MptcpSubflow Tcp where
  showConnectionText = showMptcpSubflowText
  buildFrameFromStreamId = buildSubflowFromTcpStreamId
  -- TODO use score as well
  similarityScore sf1 sf2 = similarityScore (sfConn sf1) (sfConn sf2)


-- |Show the subflow (ids)
showMptcpSubflowText :: MptcpSubflow -> Text
showMptcpSubflowText sf =
  showConnectionText (sfConn sf) <> " (Local/Remote ids: " <> tshow (sfLocalId sf)
      <> "/" <> tshow (sfRemoteId sf) <> ", token " <> tshow (sfJoinToken sf) <> ")"

-- TODO add sthg in case it's the master subflow ?
showConnection :: StreamConnection a b => a -> String
showConnection = T.unpack . showConnectionText