module Scyther.Theory.Dot (
dotSequentMarked
, dotProtocol
, graphvizDotToPng
) where
import Safe
import Data.Maybe
import Data.List (find)
import qualified Data.Set as S
import qualified Data.Map as M
import Data.Color
import Control.Basics
import Control.Monad.State
import Control.Monad.Reader
import Control.Concurrent.Chan
import Text.Dot as Dot
import Text.Isar hiding (hcat)
import System.Process (waitForProcess, runCommand)
import qualified Scyther.Equalities as E
import Scyther.Facts
import Scyther.Sequent
findWithError :: Ord k => String -> k -> M.Map k v -> v
findWithError msg k = fromMaybe (error msg) . M.lookup k
findShowError :: (Ord k, Show k) => k -> M.Map k v -> v
findShowError k =
findWithError ("findShowError: key '"++show k++"' not in map.") k
threadEdgeStyle :: [(String, String)]
threadEdgeStyle = [("style","bold"),("weight","10.0")]
setDefaultAttributes :: Dot ()
setDefaultAttributes = do
attribute ("nodesep","0.3")
attribute ("ranksep","0.3")
nodeAttributes [("fontsize","10"),("fontname","Helvetica"),("width","0.3"),("height","0.2")]
edgeAttributes [("fontsize","10"),("fontname","Helvetica")]
getColorWithDefault :: TID -> M.Map (Maybe TID) v -> v
getColorWithDefault tid m = M.findWithDefault (m M.! Nothing) (Just tid) m
type TIDMap = M.Map TID (Maybe Role)
extractTIDMap :: Sequent -> TIDMap
extractTIDMap se = formulaMap (seConcl se) (M.fromList fromPrem)
where
prem = sePrem se
fromPrem = [ (tid, threadRole tid prem) | tid <- quantifiedTIDs prem ]
formulaMap :: Formula -> TIDMap -> TIDMap
formulaMap formula = case formula of
(FExists (Left tid) prop) -> formulaMap prop . M.insert tid Nothing
(FExists (Right _ ) prop) -> formulaMap prop
(FConj lprop rprop) -> formulaMap lprop . formulaMap rprop
(FAtom (AEq (E.TIDRoleEq (tid, role)))) -> M.insert tid (Just role)
_ -> id
type ColorMap = M.Map (Maybe TID) String
intruderTID :: TID
intruderTID = 1
intruderHue :: Double
intruderHue = 18 / 360
threadColors :: Sequent -> ColorMap
threadColors se = M.fromList $ assocs
where
optRoles = (1, Nothing) : (zip [2..] . map Just . protoRoles $ seProto se)
roleeqs = M.toList $ extractTIDMap se
threadsOfRole optRole = [ tid | (tid, optRole') <- roleeqs, optRole == optRole' ]
tidGroupEqs :: [[(TID, (Int, Int))]]
tidGroupEqs =
[(intruderTID, (0,0))] :
[ [ (tid, (roleIdx, tidIdx))
| (tidIdx, tid) <- zip [0..] $ threadsOfRole optRole ]
| (roleIdx, optRole) <- optRoles ]
getColor idx =
maybe (error $ "color of " ++ show idx ++ " not found") snd $
find ((idx ==) . fst) $ colors
colors = colorGroups intruderHue (map length tidGroupEqs)
assocs = (Nothing, "#eeeeee") :
[ (Just tid, hsvToHex $ getColor idx) | (tid, idx) <- concat tidGroupEqs ]
type NodeMap = M.Map Event NodeId
data DotEnv = DotEnv {
colorMap :: ColorMap
, threadMapping :: E.Mapping
, marked :: S.Set Event
}
data DotState = DotState {
nodeMap :: M.Map Event NodeId
, threadAtoms :: M.Map (Maybe TID) [Atom]
}
mapNodeMap :: (NodeMap -> NodeMap) -> DotState -> DotState
mapNodeMap f s = s { nodeMap = f (nodeMap s) }
type MyDot = ReaderT DotEnv (StateT DotState Dot)
runMyDot :: ColorMap -> E.Mapping -> S.Set Event -> MyDot a -> Dot a
runMyDot cm tm ma m = evalStateT (runReaderT m env0) s0
where
env0 = DotEnv { colorMap = cm, threadMapping = tm, marked = ma }
s0 = DotState { nodeMap = M.empty, threadAtoms = M.empty }
liftDot :: Dot a -> MyDot a
liftDot = lift . lift
liftDot1 :: (Dot (a, DotState) -> Dot (b, DotState)) -> (MyDot a -> MyDot b)
liftDot1 f m = ReaderT $ \env -> StateT $ \s ->
f (runStateT (runReaderT m env) s)
myCluster :: MyDot a -> MyDot (NodeId, a)
myCluster = liftDot1 (liftM adapt . cluster)
where adapt (nid, (x, s)) = ((nid, x), s)
dotEvent :: Event -> MyDot NodeId
dotEvent ev = do
optId <- gets $ M.lookup ev . nodeMap
case optId of
Just nid -> return nid
Nothing -> do
mapping <- asks threadMapping
mark <- asks $ S.member ev . marked
let peripheries | mark = "2"
| otherwise = "1"
label = render $ sptRawEvent mapping ev
shape = case ev of
Learn _ -> "ellipse"
Step _ _ -> "box"
color <- case ev of
Learn _ -> asks $ getColorWithDefault intruderTID . colorMap
Step tid _ -> asks $ getColorWithDefault tid . colorMap
nid <- liftDot $ node
[ ("label", label)
, ("shape", shape)
, ("style", "filled")
, ("fillcolor", color)
, ("peripheries", peripheries)
]
modify . mapNodeMap $ M.insert ev nid
return nid
dotAtom :: Atom -> MyDot ()
dotAtom (AEv ev) = dotEvent ev >> return ()
dotAtom (AEvOrd evord@(from, to)) = do
fromId <- dotEvent from
toId <- dotEvent to
liftDot $ edge fromId toId edgestyle
where
edgestyle = case evord of
(Step tid1 _, Step tid2 _) | tid1 == tid2 -> threadEdgeStyle
_ -> []
dotAtom atom = addThreadAtom atom
resetThreadAtoms :: [(Maybe TID, Maybe Role)] -> MyDot ()
resetThreadAtoms info = do
s <- get
put $ s { threadAtoms = M.fromList $ mapMaybe prepare info }
where
prepare (optTid, optRole) = do
role <- optRole
tid <- optTid
return (optTid, [AEq (E.TIDRoleEq (tid, role))])
addThreadAtom :: Atom -> MyDot ()
addThreadAtom atom = do
s <- get
let ins = M.insertWith (++) (headMay $ atomTIDs atom) [atom]
put $ s { threadAtoms = ins $ threadAtoms s }
dotThreads :: Bool
-> MyDot ()
dotThreads quantified = do
threadInfo <- gets $ M.toList . threadAtoms
mapM_ (uncurry (dotThread quantified)) threadInfo
dotThread :: Bool
-> Maybe TID -> [Atom] -> MyDot ()
dotThread quantified optTid atoms = do
mapping <- asks $ threadMapping
let
infoContent = filter (not.null)
[ threadinfo
, ppLidList "compromised: " compr
, ppLidList "uncompromised: " uncompr
, ppAtomList avareqs
, ppAtomList other
]
optRole = headMay [ role | AEq (E.TIDRoleEq (_, role)) <- atoms ]
`mplus` (do tid <- optTid
E.threadRole tid (E.getMappingEqs mapping))
quantifier | quantified = "some"
| otherwise = "thread"
threadinfo = case (fmap show optTid, fmap roleName optRole) of
(Nothing, Nothing) -> ""
(Nothing, Just role) -> "running " ++ role
(Just tid, Nothing) -> quantifier ++ " " ++ tid
(Just tid, Just role) -> quantifier ++ " " ++ tid ++ " running " ++ role
compr = [ a | ACompr a <- atoms ]
uncompr = [ a | AUncompr a <- atoms ]
avareqs = [ atom | atom@(AEq (E.AVarEq _)) <- atoms ]
other = do
atom <- atoms
guard $ case atom of
AEq eq -> case eq of
E.TIDRoleEq _ -> False
E.RoleEq _ -> False
E.TIDEq _ -> False
E.AVarEq _ -> False
_ -> True
AEv _ -> False
AEvOrd _ -> False
ACompr _ -> False
AUncompr _ -> False
_ -> True
return atom
ppAtomList [] = mzero
ppAtomList as = unlines $ map (render . sptAtom mapping) as
ppLidList _ [] = mzero
ppLidList prefix lids =
render . (text prefix <->) . fsep . punctuate comma $ map sptMessage lids
unless (null infoContent) $ do
color <- asks $ maybe (M.! Nothing) getColorWithDefault optTid . colorMap
info <- liftDot $ record_
(Dot.vcat' infoContent)
[ ("style", "filled")
, ("fillcolor", color )
]
evMap <- gets nodeMap
fromMaybe (return ()) $ do
tid <- optTid
role <- optRole
let findStep step = M.lookup (Step tid step) evMap
stepId <- msum . map findStep $ roleSteps role
return $ liftDot $ edge info stepId threadEdgeStyle
dotFacts :: Facts -> MyDot ()
dotFacts facts = do
resetThreadAtoms $
map (\tid -> (Just tid, threadRole tid facts)) (quantifiedTIDs facts)
mapM_ dotAtom $ toAtoms facts
dotThreads True
localThreadInfo :: TID -> Maybe Role -> MyDot a -> MyDot a
localThreadInfo tid optRole m = do
s <- get
x <- local (tryNoteRole tid optRole) $ m
let optTid = Just tid
restoreAtoms = maybe (M.delete optTid) (M.insert optTid)
(M.lookup optTid (threadAtoms s))
modify $ \s' -> s' { threadAtoms = restoreAtoms $ threadAtoms s' }
return x
tryNoteRole :: TID -> Maybe Role -> DotEnv -> DotEnv
tryNoteRole _ Nothing env = env
tryNoteRole tid (Just role) env =
env { threadMapping = E.addTIDRoleMapping tid role (threadMapping env) }
dotFormula :: Formula -> MyDot ()
dotFormula (FAtom atom) = dotAtom atom
dotFormula (FConj f1 f2) = dotFormula f1 >> dotFormula f2
dotFormula (FExists (Left tid) inner) = do
localThreadInfo tid (findRole tid inner) $ do
dotFormula inner
optAtoms <- gets $ M.lookup (Just tid) . threadAtoms
maybe (return ()) (dotThread True (Just tid)) optAtoms
dotFormula (FExists (Right aid) inner) = do
_ <- liftDot $ node [("label", "some agent " ++ show aid)]
dotFormula inner
dotConclusion :: Formula -> MyDot ()
dotConclusion formula = do
resetThreadAtoms []
dotFormula formula
dotThreads False
dotSequent :: Sequent -> MyDot ()
dotSequent se = do
s <- get
liftDot setDefaultAttributes
_ <- myCluster $ do
liftDot $ attribute ("label","premise")
dotFacts $ sePrem se
put s
_ <- myCluster $ do
liftDot $ attribute ("label","conclusion")
dotConclusion $ seConcl se
return ()
dotSequentMarked :: S.Set Event -> Sequent -> Dot ()
dotSequentMarked markedEvs se =
runMyDot (threadColors se) (eqsToMapping $ sePrem se) markedEvs $ dotSequent se
dotProtocol :: Protocol -> Dot ()
dotProtocol proto = do
return ()
setDefaultAttributes
ids <- mapM dotStep steps
let stepMap = M.fromList $ zip steps ids
mapM_ (dotSend stepMap) steps
mapM_ (dotRole stepMap) roles
where
roles = protoRoles proto
steps = concatMap extractSteps roles
extractSteps role = zip (repeat $ roleName role) (roleSteps role)
rMap = M.fromList $ zip (map roleName roles) [2..]
cMap = colorGroups intruderHue (replicate (length roles + 2) 1)
getColor gIdx eIdx =
maybe (error "color not found") snd $ find (((gIdx, eIdx) == ) . fst) cMap
getRoleColor role = hsvToHex $ getColor (findShowError role rMap) 0
mkNode label color attrs = node $ [
("label",label)
, ("shape","box")
, ("style","filled")
, ("fillcolor",color)
] ++ attrs
dotStep (role, step) =
mkNode (render $ sptRoleStep Nothing step) (getRoleColor role) []
dotSend _ (_, Recv _ _) = return ()
dotSend stepMap send@(_, Send l _) = sequence_
[ edge (findShowError send stepMap) (findShowError recv stepMap) []
| recv@(_, Recv l' _) <- steps, l == l' ]
dotRole stepMap role = do
roleId <- mkNode ("role "++roleName role) (getRoleColor $ roleName role) [("peripheries","2")]
let getStepId = (`findShowError` stepMap) . ((,) (roleName role))
ids = roleId : map getStepId (roleSteps role)
mkEdge (from, to) = edge from to threadEdgeStyle
mapM_ mkEdge (zip ids (tail ids))
graphvizDotToPng :: FilePath
-> FilePath
-> FilePath
-> Chan String
-> IO ()
graphvizDotToPng dotTool dotFile pngFile msgChan = do
let cmd = dotTool ++ " -Tpng -o " ++ pngFile ++ " " ++ dotFile
writeChan msgChan cmd
hProc <- runCommand cmd
_ <- waitForProcess hProc
return ()