packages feed

cpsa-2.0.3: src/CPSA/Lib/Cohort.hs

-- Computes the cohort associated with a skeleton or its generalization

-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.

module CPSA.Lib.Cohort (reduce, unrealized) where

import Control.Monad
import qualified Data.Set as S
import Data.Set (Set)
import qualified Data.List as L
import Data.Maybe
import CPSA.Lib.Algebra
import CPSA.Lib.Protocol
import CPSA.Lib.Strand

{-- Debugging support
import System.IO.Unsafe
import qualified CPSA.Lib.Utilities as U

z :: Show a => a -> b -> b
z x y = seq (unsafePerformIO (print x)) y

zz :: Show a => a -> a
zz x = z x x

zn :: Show a => a -> Maybe b -> Maybe b
zn x Nothing = z x Nothing
zn _ y = y

zf :: Show a => a -> Bool -> Bool
zf x False = z x False
zf _ y = y

zt :: Algebra t p g s e c => t -> String
zt t =
    show (displayTerm (addToContext emptyContext [t]) t)

zs :: Algebra t p g s e c => Set t -> String
zs s =
    show $ map (displayTerm (addToContext emptyContext ts)) ts
    where
      ts = S.toList s

zi :: Algebra t p g s e c => Instance t p g s e c -> String
zi inst =
    show (map f e)
    where
      domain = rvars (role inst)
      e = reify domain (env inst)
      range = map snd e
      f (x, t) = (displayTerm (context domain) x,
                  displayTerm (context range) t)
      context ts = addToContext emptyContext ts
--}

derivable :: Algebra t p g s e c => Set t -> Set t -> t -> Bool
derivable avoid sent term =
    let (knowns, unknowns) = decompose sent avoid in
    buildable knowns unknowns term

-- Returns the nodes in a preskeleton that are not realized.
unrealized :: Algebra t p g s e c => Preskel t p g s e c -> [Node]
unrealized k =
    foldl unrealizedInStrand [] (strands k)
    where
      (a, _) = avoid k
      unrealizedInStrand acc s =
          fst $ foldl unrealizedInNode (acc, S.empty) (nodes s)
      unrealizedInNode (acc, ns) n =
          case event n of
            Out _ -> (acc, ns)
            In t ->
                let ns' = addSendingBefore ns n
                    ts = S.map (evtTerm . event) ns' in
                case derivable a ts t of
                  True -> (acc, ns')
                  False -> (graphNode n : acc, ns')

addSendingBefore :: Algebra t p g s e c => Set (Vertex t p g s e c) ->
                    Vertex t p g s e c  -> Set (Vertex t p g s e c)
addSendingBefore s n =
    foldl addSending s (preds n)
    where
      addSending s n
        | S.member n s = s
        | otherwise = addSendingBefore (addIfSending s n) n
      addIfSending s n =
          case event n of
            In _ -> s
            Out _ -> S.insert n s

-- Returns that atoms that cannot be guess when determining if a
-- term is derivable from some other terms, and the atoms that
-- uniquely originate in this skeleton.
avoid :: Algebra t p g s e c => Preskel t p g s e c -> (Set t, [t])
avoid k =
    (S.union (S.fromList (knon k)) (S.fromList u), u)
    where
      u = uniqOrig k

-- Suppose k --v,t-> k', where k |-pi,sigma-> k'.  Let t'=sigma(t),
-- T=sigma(esc(k, v, t)), and t"=msg(k', pi(v)).  Term t is solved in
-- k' from k at v if:
--
-- 1. t' is carried only within T in t", or
--
-- 2. for some t in outpred(k', pi(v)), t' is not carried only within
--    T in t, or
--
-- 3. the decryption key for an element of T is derivable, or
--
-- 4. t' is an encryption and the encryption key for t' is derivable.
solved :: Algebra t p g s e c => t -> Maybe t -> Set t ->
          Preskel t p g s e c -> Node -> s -> Bool
solved ct ek escape k (s, p) subst =
    carriedOnlyWithin ct' escape' t ||
    any (not . carriedOnlyWithin ct' escape') (S.toList ts) ||
    any (maybe False (derivable a ts) . decryptionKey) (S.toList escape') ||
    maybe False (derivable a ts) ek
    where
      v = nodes (strands k !! s) !! p -- Look up vertex in k
      t = evt id err (event v)        -- Term at v
      err = const $ error "Cohort.solved: got an outbound term"
      ct' = substitute subst ct       -- Mapped critical term
      escape' = S.map (substitute subst) escape
      vs = addSendingBefore S.empty v
      ts = S.map (evtTerm . event) vs -- Outbound predecessors
      (a, _) = avoid k

-- This switch controls the use of solved filtering.
useSolvedFilter :: Bool
useSolvedFilter = True

maybeSolved :: Algebra t p g s e c => t -> Maybe t -> Set t ->
               Preskel t p g s e c -> Node -> s -> Bool
maybeSolved ct ek escape k n subst =
    not useSolvedFilter || solved ct ek escape k n subst

-- Abort if there is an unrealized node without a test, otherwise
-- return a list of skeletons that solve one test.  If the skeleton is
-- realized, try to generalize it, but only when noIsoChk is false.
reduce :: Algebra t p g s e c => Bool -> Preskel t p g s e c ->
          [Preskel t p g s e c]
reduce noIsoChk k =
    firstJust (map (testStrand k u a) ss)
                  (whenRealized k) -- Skeleton is realized
    where
      ss = reverse (strands  k) -- Visit recently added strands first
      (a, u) = avoid k
      whenRealized k = if noIsoChk then [] else realizedReductions k

-- These reductions use isomorphism checking.
realizedReductions :: Algebra t p g s e c => Preskel t p g s e c ->
                      [Preskel t p g s e c]
realizedReductions k =
    let ks = maximize k in
    if null ks then             -- If k is a shape, collapse it
        collapse k
    else                        -- else generalize it
        ks

-- Returns the first Just value in a list or the default when there is
-- none.
firstJust :: [Maybe a] -> a -> a
firstJust [] x = x
firstJust (Just x : _) _ = x
firstJust (Nothing : xs) x = firstJust xs x

-- Look for a test node in a strand
testStrand :: Algebra t p g s e c => Preskel t p g s e c ->
              [t] -> Set t -> Strand t p g s e c ->
              Maybe [Preskel t p g s e c]
testStrand k u a s =
    loop S.empty (nodes s)
    where
      loop _ [] = Nothing
      loop ns (n : nodes) =
          case event n of
            Out _ -> loop ns nodes
            In t ->
                let ns' = addSendingBefore ns n
                    ts = S.map (evtTerm . event) ns'
                    (ts', a') = decompose ts a
                    der = buildable ts' a' in -- Derivable before node
                if der t then
                    loop ns' nodes
                else
                    Just $ testNode k u ts der (graphNode n) t

useEncryptionFirstOrder :: Bool
useEncryptionFirstOrder = True

-- Look for a critical term that makes this node a test node.
testNode :: Algebra t p g s e c => Preskel t p g s e c ->
            [t] -> Set t -> (t -> Bool) -> Node -> t ->
            [Preskel t p g s e c]
testNode k u ts derivable n t =
    loop cts
    where
      loop [] = error ("Cohort.testNode missing test at " ++ show n)
      loop ((ct, ek) : cts) =
          case escapeSet ts derivable ct of
            Nothing -> loop cts
            Just escape ->
                if carriedOnlyWithin ct escape t then
                    loop cts
                else
                    solveNode k ct ek n t escape
      cts = 
          if useEncryptionFirstOrder then
              map g (filter h (encryptions t)) ++
              map f (filter (flip carriedBy t) u)
          else
              map f (filter (flip carriedBy t) u) ++
              map g (filter h (encryptions t))
      f ct = (ct, Nothing)
      g (ct, ek) = (ct, Just ek)
      h (_, ek) = not (derivable ek)

-- Compute the escape set
escapeSet :: Algebra t p g s e c => Set t ->
             (t -> Bool) -> t -> Maybe (Set t)
escapeSet ts derivable ct =
    foldM f S.empty (S.toList ts)
    where
      f e t =
          do
            es <- protectors derivable ct t
            return (foldl (flip S.insert) e es)

carriedOnlyWithin :: Algebra t p g s e c => t -> Set t -> t -> Bool
carriedOnlyWithin target escape source =
    existsOnAllPathsToTarget (flip S.member escape) target source

-- existsOnAllPathsToTarget pred target source is true if the
-- predicate is true for some term on a path for every path from the
-- source to the target
existsOnAllPathsToTarget :: Algebra t p g s e c =>
                            (t -> Bool) -> t -> t -> Bool
existsOnAllPathsToTarget pred target source =
    all (any pred) (map (ancestors source) (carriedPlaces target source))

solveNode :: Algebra t p g s e c => Preskel t p g s e c ->
             t -> Maybe t -> Node -> t -> Set t ->
             [Preskel t p g s e c]
solveNode k ct ek n t escape =
    cons ++ augs ++ cwos ++ lsns
    where
      cons = contractions k ct ek n t escape cause
      augs = augmentations  k ct ek n escape cause
      cwos =
          if useContractionsWithOrdering then
              contractionsWithOrdering k ct ek n escape cause
          else
              []
      lsns = addListeners k ct ek n t escape cause
      cause = Cause (dir ek) n ct escape

useContractionsWithOrdering :: Bool
useContractionsWithOrdering = False

-- Contractions

contractions :: Algebra t p g s e c => Preskel t p g s e c ->
                t -> Maybe t -> Node -> t -> Set t ->
                Cause t p g s e c -> [Preskel t p g s e c]
contractions k ct ek n t escape cause =
    [ k | subst <- mgus $ cows ct escape t (gen k, emptySubst),
          (k, n, subst') <- maybeToList $ contract k n cause subst,
          maybeSolved ct ek escape k n subst' ]

cows :: Algebra t p g s e c => t -> Set t -> t -> (g, s) -> [(g, s)]
cows ct escape t subst =
    if carriedOnlyWithin ct escape t then
        [subst]
    else
        concatMap f (cowf ct escape t [subst])
        where
          f (gen, subst) =
              let ct' = substitute subst ct
                  escape' = S.map (substitute subst) escape
                  t' = substitute subst t in
              cows ct' escape' t' (gen, subst)

cowf :: Algebra t p g s e c => t -> Set t -> t -> [(g, s)] -> [(g, s)]
cowf ct escape t substs =
    foldl f substs (carriedPlaces ct t)
    where
      f substs p =
          [ s |
            a <- ancestors t p,
            e <- S.toList escape,
            subst <- substs,
            s <- maybeToList $ unify a e subst ]

useMguFilter :: Bool
useMguFilter = True

-- Filter out non-most general unifiers
mgus :: Algebra t p g s e c => [(g, s)] -> [(g, s)]
mgus substs =
    if useMguFilter then
        loop substs []
    else
        substs
    where
      loop [] acc = acc
      loop (subst : substs) acc
          | any (f subst) substs || any (f subst) acc =
              loop substs acc
          | otherwise = loop substs (subst : acc)
      f subst subst' = moreGeneral subst' subst

{-
-- Given finite maps f and g, find all maps h such that f = h o g
-- -1 is a map is the wildcard.

maps :: [Int] -> [Int] -> Maybe [Int]
maps f g =
    loop f g [] (-1)

loop :: [Int] -> [Int] -> [(Int, Int)] -> Int -> Maybe [Int]
loop [] [] h n =
    return $ map (maybe (-1) id . flip lookup h) (nats (n + 1))
loop (x:xs) (y:ys) h n =
    case lookup y h of
      Nothing -> loop xs ys ((y,x) : h) (max y n)
      Just x' | x' == x -> loop xs ys h n
              | otherwise -> Nothing
loop _ _ _ _ = Nothing
-}

dir :: Maybe a -> Direction
dir Nothing = Nonce
dir _ = Encryption

-- Augmentations

augmentations :: Algebra t p g s e c => Preskel t p g s e c ->
                t -> Maybe t -> Node -> Set t ->
                Cause t p g s e c -> [Preskel t p g s e c]
augmentations k ct ek n escape cause =
    [ k' | r <- roles (protocol k),
           k' <- roleAugs k ct ek n escape cause r ]

roleAugs :: Algebra t p g s e c => Preskel t p g s e c ->
            t -> Maybe t -> Node -> Set t -> Cause t p g s e c ->
            Role t p g s e c -> [Preskel t p g s e c]
roleAugs k ct ek n escape cause role =
    [ k' | (subst', inst) <- transformingNode ct escape role subst,
           (k', n', subst'') <-
               maybeToList $ augment k n cause role subst' inst,
           maybeSolved ct ek escape k' n' subst'' ]
    where
      subst = cloneRoleVars (gen k) role

-- Generate a fresh set of role variables
cloneRoleVars :: Algebra t p g s e c => g -> Role t p g s e c -> (g, s)
cloneRoleVars gen role =
    grow (rvars role) gen emptySubst
    where
      grow [] gen subst = (gen, subst)
      grow (t : ts) gen subst =
          let (gen', t') = clone gen t in
          case unify t t' (gen', subst) of
            Nothing -> error "Cohort.grow: Internal error"
            Just (gen'', subst') -> grow ts gen'' subst'

transformingNode :: Algebra t p g s e c => t -> Set t ->
                    Role t p g s e c -> (g, s) ->
                    [((g, s), Instance t p g s e c)]
transformingNode ct escape role subst =
    loop 1 [] [] (rtrace role)
    where
      targets = S.toList (targetTerms ct escape)
      -- loop height past acc trace
      loop _ _ acc [] = acc
      loop ht past acc (In t : c) =
          loop (ht + 1) (In t : past) acc c
      loop ht past acc (Out t : c) =
          loop (ht + 1) (Out t : past) acc' c
          where
            substs = carriedBindings targets t subst
            substs' =
                if useMguFilter then
                    concat [ mgus $ cowt ct escape past [s] | s <- substs]
                else
                    cowt ct escape past substs
            acc' = maybeAug ct escape role ht substs' acc t

-- Terms considered for binding with the carried terms in an outbound
-- term.
targetTerms :: Algebra t p g s e c => t -> Set t -> Set t
targetTerms ct escape =
    S.difference (S.fold f (S.singleton ct) escape) escape
    where
      f t ts =
          foldl (flip S.insert) ts
                (concatMap (ancestors t) (carriedPlaces ct t))

-- Find bindings for terms in the test.
carriedBindings :: Algebra t p g s e c => [t] -> t -> (g, s) -> [(g, s)]
carriedBindings targets outbound subst =
    [ s |
      subterm <- S.toList (foldCarriedTerms (flip S.insert) S.empty outbound),
      target <- targets,
      s <- maybeToList $ unify subterm target subst ]

-- Ensure the critical term is carried only within the escape set of
-- every term in the past using cows.
cowt :: Algebra t p g s e c => t -> Set t ->
        Trace t p g s e c -> [(g, s)] -> [(g, s)]
cowt _ _ [] substs = substs
cowt ct escape (evt : c) substs =
    cowt ct escape c (concatMap f substs)
    where
      f (gen, subst) =
          let ct' = substitute subst ct
              escape' = S.map (substitute subst) escape
              t' = substitute subst (evtTerm evt) in
          cows ct' escape' t' (gen, subst)

-- If the outbound term is carried only within, no transforming node
-- was found, otherwise, add a candidate augmentation to the
-- accumulator.
maybeAug :: Algebra t p g s e c => t -> Set t ->
            Role t p g s e c -> Int -> [(g, s)] ->
            [((g, s), Instance t p g s e c)] -> t ->
            [((g, s), Instance t p g s e c)]
maybeAug ct escape role ht substs acc t =
    foldl f acc $ L.filter testNotSolved substs
    where
      testNotSolved (_, subst) =
          not $ carriedOnlyWithin
                  (substitute subst ct)
                  (S.map (substitute subst) escape)
                  (substitute subst t)
      f acc (gen, subst) =
          maybe acc (\(gen, inst) -> ((gen, subst), inst) : acc) inst'
          where
            inst' = bldInstance role itrace gen
            itrace = map (evtMap $ substitute subst) (take ht (rtrace role))

-- Contractions with an ordering

contractionsWithOrdering :: Algebra t p g s e c => Preskel t p g s e c ->
                            t -> Maybe t -> Node -> Set t ->
                            Cause t p g s e c -> [Preskel t p g s e c]
contractionsWithOrdering k ct ek n escape cause =
    [ k' | (n', t, past) <- copresent k n,
           subst <- carriedBindings targets t (gen k, emptySubst),
           subst' <-  mgus $ cowp ct escape past [subst],
           testNotSolved t subst',
           (k', n'', subst'') <-
               maybeToList $ contractAndOrder k n cause subst' n',
           maybeSolved ct ek escape k' n'' subst'' ]
    where
      targets = S.toList (targetTerms ct escape)
      testNotSolved t (_, subst) =
          not $ carriedOnlyWithin
                  (substitute subst ct)
                  (S.map (substitute subst) escape)
                  (substitute subst t)

cowp :: Algebra t p g s e c => t -> Set t ->
        [t] -> [(g, s)] -> [(g, s)]
cowp _ _ [] substs = substs
cowp ct escape (t : ts) substs =
    cowp ct escape ts (concatMap f substs)
    where
      f (gen, subst) =
          let ct' = substitute subst ct
              escape' = S.map (substitute subst) escape
              t' = substitute subst t in
          cows ct' escape' t' (gen, subst)

-- Listener augmentations

addListeners :: Algebra t p g s e c => Preskel t p g s e c ->
                t -> Maybe t -> Node -> t -> Set t ->
                Cause t p g s e c -> [Preskel t p g s e c]
addListeners k ct ek n t escape cause =
    [ k' | t' <- filter (/= t) (S.toList (escapeKeys ek escape)),
           (k', n', subst) <- maybeToList $ addListener k n cause t',
           maybeSolved ct ek escape k' n' subst ]

escapeKeys :: Algebra t p g s e c => Maybe t -> Set t -> Set t
escapeKeys ek escape =
    S.fold f e escape
    where
      f e s = maybe s (flip S.insert s) (decryptionKey e)
      e = maybe S.empty S.singleton ek

-- Maximize a realized skeleton if possible

maximize :: Algebra t p g s e c => Preskel t p g s e c ->
            [Preskel t p g s e c]
maximize k =
    loop (generalize k)         -- Generalize generates candidates
    where
      loop [] = []              -- Realized skeleton k is a shape
      loop ((k', mapping) : rest) =
          case specialization k k' mapping of -- Test a candidate
            Nothing -> loop rest   -- Not a specialization, try again
            Just k' -> [k']        -- Found a more minimal skeleton

-- Test to see if realized skeleton k is a specialization of
-- preskeleton k' using the given strand mapping.  Returns the
-- skeleton associated with k' if it refines k.

specialization :: Algebra t p g s e c => Preskel t p g s e c ->
                  Preskel t p g s e c -> [Sid] ->
                  Maybe (Preskel t p g s e c)
specialization k k' mapping
    | not (preskelWellFormed k') = Nothing
    | otherwise =
        do
          k'' <- toSkeleton False k'
          case realized k'' && gist k /= gist k'' &&
               refines k'' (pov k'') (prob k'') &&
               refines k (Just k') mapping of
            True -> Just k''
            False -> Nothing
        where
          realized = null . unrealized
          refines _ Nothing _ =
              error "Cohort.specialization: cannot find point of view"
          refines k (Just k') mapping =
              maybe False (const True) (homomorphism k' k mapping)