cpsa-2.0.0: src/CPSA/Annotations/Annotations.hs
-- Adds annotations to preskeletons
-- 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.Annotations.Annotations (Prot, annotations) where
import Control.Monad
import qualified Data.List as L
import qualified Data.Set as S
import CPSA.Lib.CPSA
import CPSA.Annotations.Formulas
{--
import System.IO.Unsafe
z :: Show a => a -> b -> b
z x y = seq (unsafePerformIO (print x)) y
--}
-- Constructs the output or reports an error from the input given a
-- protocol name, and its algebra specific starting variable
-- generator. The third argument is the protocol information required
-- for processing preskeletons.
annotations :: (Algebra t p g s e c, Monad m) => String -> g ->
[Prot t p g s e c] -> SExpr Pos ->
m ([Prot t p g s e c], SExpr Pos)
annotations name origin ps x@(L pos (S _ "defprotocol" : xs)) =
do
p <- loadProt name origin pos xs
return (p : ps, x)
annotations _ _ ps (L pos (S _ "defskeleton" : xs)) =
do
y <- findPreskel pos ps xs
return (ps, y)
annotations _ _ ps x = return (ps, x)
-- Load a protocol
-- The Prot record contains information extraced from protocols for
-- use when processing preskeletons.
data Algebra t p g s e c => Prot t p g s e c = Prot
{ pname :: String, -- Protocol name
gen :: g, -- Generator for preskeletons
roles :: [Role t p g s e c] }
deriving Show
-- The Role record contains information extraced from roles for use
-- when processing preskeletons.
data Algebra t p g s e c => Role t p g s e c = Role
{ rname :: String, -- Role name
vars :: [t], -- Declared variables
prin :: t, -- Principal
forms :: [Formula t p g s e c] } -- Annotations
deriving Show
-- Load a protocol. On success, returns a Prot record.
loadProt :: (Algebra t p g s e c, Monad m) => String -> g ->
Pos -> [SExpr Pos] -> m (Prot t p g s e c)
loadProt nom origin pos (S _ name : S _ alg : x : xs)
| alg /= nom =
fail (shows pos $ "Expecting terms in algebra " ++ nom)
| otherwise =
do
(gen, rs) <- loadRoles origin (x : xs)
return (Prot { pname = name, gen = gen, roles = rs })
loadProt _ _ pos _ =
fail (shows pos "Malformed protocol")
-- A generator is threaded thoughout the protocol loading process so
-- as to ensure no variable occurs in two roles. It also ensures that
-- every variable that occurs in a preskeleton never occurs in one of
-- its roles.
loadRoles :: (Algebra t p g s e c, Monad m) => g ->
[SExpr Pos] -> m (g, [Role t p g s e c])
loadRoles origin xs =
mapAccumLM loadRole origin xs
loadRole :: (Algebra t p g s e c, Monad m) => g ->
SExpr Pos -> m (g, Role t p g s e c)
loadRole gen (L pos (S _ "defrole" :
S _ name :
L _ (S _ "vars" : vars) :
L _ (S _ "trace" : _ : c) :
rest)) =
do
(gen, vars) <- loadDecls gen vars
let len = 1 + length c -- Length of the trace
let annos = assoc annotationsKey rest
(gen, prin, forms) <- loadFormulas pos vars len gen annos
let r = Role { rname = name, vars = vars, prin = prin, forms = forms }
return (gen, r)
loadRole _ x =
fail (shows (annotation x) "Malformed role")
loadFormulas :: (Algebra t p g s e c, Monad m) => Pos -> [t] -> Int ->
g -> [SExpr Pos] -> m (g, t, [Formula t p g s e c])
loadFormulas pos vars len gen (x : xs) =
do
prin <- loadTerm vars x
case isBaseTerm prin of
True -> return ()
False -> fail (shows pos "principal not an atom")
(g, alist) <- mapAccumLM (loadIndexedFormula vars len) gen xs
checkIndices pos alist
return (g, prin, map (getNth alist) (nats len))
where
getNth alist i = maybe true id (lookup i alist)
loadFormulas pos _ _ _ [] =
fail (shows pos "Role missing annotations")
loadIndexedFormula :: (Algebra t p g s e c, Monad m) => [t] -> Int ->
g -> SExpr Pos -> m (g, (Int, Formula t p g s e c))
loadIndexedFormula _ len _ (L _ [N pos i, _])
| i < 0 || i >= len = fail (shows pos "Bad index for formula")
loadIndexedFormula vars _ gen (L _ [N _ i, form]) =
do
(g, f) <- loadFormula vars gen form
return (g, (i, f))
loadIndexedFormula _ _ _ x =
fail (shows (annotation x) "Malformed indexed formula")
-- Ensure there are no formulas with the same index
checkIndices :: (Algebra t p g s e c, Monad m) => Pos ->
[(Int, Formula t p g s e c)] -> m ()
checkIndices _ [] = return ()
checkIndices pos ((i, _) : alist)
| any ((== i) . fst) alist =
fail (shows pos $ showString "Duplicate index " $ show i)
| otherwise = checkIndices pos alist
-- Load a preskeleton
data Algebra t p g s e c => Instance t p g s e c = Instance
-- Role from which this was instantiated (Nothing for listeners)
{ pos :: Pos, -- Instance position
role :: Maybe (Role t p g s e c),
env :: e, -- The environment
height :: Int } -- Height of the instance
deriving Show
type Strands = [Int] -- [Strand height]
type Trace = [Bool] -- Directions of terms in trace
type Node = (Int, Int) -- (Strand, Position)
type Pair = (Node, Node) -- Precedes relation
data Algebra t p g s e c => Preskel t p g s e c = Preskel
-- Role from which this was instantiated (Nothing for listeners)
{ protocol :: Prot t p g s e c,
insts :: [Instance t p g s e c],
traces :: [Trace],
orderings :: [Pair] }
-- Find protocol and then load preskeleton.
-- Remove any old annotations, and add new ones.
findPreskel :: (Algebra t p g s e c, Monad m) => Pos ->
[Prot t p g s e c] -> [SExpr Pos] -> m (SExpr Pos)
findPreskel pos ps (S _ name : xs) =
case L.find (\p -> name == pname p) ps of
Nothing -> fail (shows pos $ "Protocol " ++ name ++ " unknown")
Just p ->
do
xs <- updatePreskel pos p xs
return (L pos (S pos "defskeleton" : S pos name : xs))
findPreskel pos _ _ = fail (shows pos "Malformed skeleton")
updatePreskel :: (Algebra t p g s e c, Monad m) => Pos ->
Prot t p g s e c -> [SExpr Pos] -> m [SExpr Pos]
updatePreskel pos prot (vs@(L _ (S _ "vars" : vars)) : xs)
| not (realized xs) || not (hasKey tracesKey xs) =
return (vs : xs) -- Don't annotate
| otherwise =
do
(gen', kvars) <- loadDecls (gen prot) vars
k <- loadPreskel prot gen' kvars xs
let xs' = strip annotationsKey (strip obligationsKey xs)
annos <- mapM instAnnos (insts k)
obls <- obligations pos k annos
let xs'' = xs' ++ [addPos pos $ displayAnnos kvars annos,
addPos pos $ displayObls kvars obls]
return (vs : xs'')
updatePreskel pos _ _ = fail (shows pos "Malformed skeleton")
realized :: [SExpr a] -> Bool
realized xs =
null (assoc "unrealized" xs) && hasKey "unrealized" xs
loadPreskel :: (Algebra t p g s e c, Monad m) => Prot t p g s e c ->
g -> [t] -> [SExpr Pos] -> m (Preskel t p g s e c)
loadPreskel prot gen kvars xs =
do
traces <- mapM loadTrace (assoc tracesKey xs)
insts <- loadInsts prot gen kvars [] xs
let heights = map height insts
orderings <- loadOrderings heights (assoc precedesKey xs)
return (Preskel { protocol = prot,
insts = insts,
traces = traces,
orderings = orderings })
loadInsts :: (Algebra t p g s e c, Monad m) => Prot t p g s e c ->
g -> [t] -> [Instance t p g s e c] -> [SExpr Pos] ->
m [Instance t p g s e c]
loadInsts prot gen kvars insts (L pos (S _ "defstrand" : x) : xs) =
case x of
S _ role : N _ height : env ->
do
(gen', i) <- loadInst pos prot gen kvars role height env
loadInsts prot gen' kvars (i : insts) xs
_ ->
fail (shows pos "Malformed defstrand")
loadInsts prot kvars gen insts (L pos (S _ "deflistener" : x) : xs) =
case x of
[_] ->
do
let i = Instance { pos = pos, role = Nothing,
env = emptyEnv, height = 2 }
loadInsts prot kvars gen (i : insts) xs
_ ->
fail (shows pos "Malformed deflistener")
loadInsts _ _ _ insts _ =
return (reverse insts)
loadInst :: (Algebra t p g s e c, Monad m) => Pos -> Prot t p g s e c ->
g -> [t] -> String -> Int -> [SExpr Pos] ->
m (g, Instance t p g s e c)
loadInst pos prot gen kvars role height env =
do
r <- lookupRole pos prot role
case height < 1 || height > length (forms r) of
True -> fail (shows pos "Bad height")
False -> return ()
(gen', env) <- foldM (loadMaplet kvars (vars r)) (gen, emptyEnv) env
return (gen', Instance { pos = pos, role = Just r,
env = env, height = height })
lookupRole :: (Algebra t p g s e c, Monad m) => Pos ->
Prot t p g s e c -> String -> m (Role t p g s e c)
lookupRole pos prot role =
case L.find (\r -> role == rname r) (roles prot) of
Nothing ->
fail (shows pos $ "Role " ++ role ++ " not found in " ++ pname prot)
Just r -> return r
loadMaplet :: (Algebra t p g s e c, Monad m) =>
[t] -> [t] -> (g, e) -> SExpr Pos -> m (g, e)
loadMaplet kvars vars env (L pos [domain, range]) =
do
t <- loadTerm vars domain
t' <- loadTerm kvars range
case match t t' env of
Nothing -> fail (shows pos "Domain does not match range")
Just env' -> return env'
loadMaplet _ _ _ x = fail (shows (annotation x) "Malformed maplet")
-- Load a trace
loadTrace :: Monad m => SExpr Pos -> m Trace
loadTrace (L _ xs) = mapM loadDt xs
loadTrace x = fail (shows (annotation x) "Malformed trace")
loadDt :: Monad m => SExpr Pos -> m Bool
loadDt (L _ [S _ "recv", _]) = return False
loadDt (L _ [S _ "send", _]) = return True
loadDt (L pos [S _ dir, _]) =
fail (shows pos $ "Unrecognized direction " ++ dir)
loadDt x = fail (shows (annotation x) "Malformed direction")
-- Load the node orderings
loadOrderings :: Monad m => Strands -> [SExpr Pos] -> m [Pair]
loadOrderings strands xs =
foldM f [] xs
where
f ns x =
do
np <- loadPair strands x
return (adjoin np ns)
loadPair :: Monad m => [Int] -> SExpr Pos -> m Pair
loadPair heights (L pos [x0, x1]) =
do
n0 <- loadNode heights x0
n1 <- loadNode heights x1
case sameStrands n0 n1 of -- Same strand
True -> fail (shows pos "Malformed pair -- nodes in same strand")
False -> return (n0, n1)
where
sameStrands (s0, _) (s1, _) = s0 == s1
loadPair _ x = fail (shows (annotation x) "Malformed pair")
loadNode :: Monad m => [Int] -> SExpr Pos -> m Node
loadNode heights (L pos [N _ s, N _ p])
| s < 0 = fail (shows pos "Negative strand in node")
| p < 0 = fail (shows pos "Negative position in node")
| otherwise =
case height heights s of
Nothing -> fail (shows pos "Bad strand in node")
Just h | p < h -> return (s, p)
_ -> fail (shows pos "Bad position in node")
where
height [] _ = Nothing
height (x: xs) s -- Assume s non-negative
| s == 0 = Just x
| otherwise = height xs (s - 1)
loadNode _ x = fail (shows (annotation x) "Malformed node")
-- Formula instantiation
type Annotations t p g s e c = Maybe (t, [Formula t p g s e c])
-- Construct the annotations for an instance
instAnnos :: (Algebra t p g s e c, Monad m) =>
Instance t p g s e c -> m (Annotations t p g s e c)
-- A listener has no annotations
instAnnos (Instance { role = Nothing }) = return Nothing
instAnnos i@(Instance { pos = pos, role = Just r }) =
do
t <- transTerm pos (vars r) (env i) (prin r)
fs <- mapM instAnno (take (height i) (forms r))
return (Just (t, fs))
where
instAnno f =
transForm pos (vars r) (env i) f
showsTerm :: Algebra t p g s e c => [t] -> t -> ShowS
showsTerm vars t = shows (displayTerm (varsContext vars) t)
varsContext :: Algebra t p g s e c => [t] -> c
varsContext vars =
addToContext emptyContext vars
-- Instantiate term and ensure all the free variables have been mapped.
transTerm :: (Algebra t p g s e c, Monad m) => Pos -> [t] -> e -> t -> m t
transTerm pos vars env t =
let t' = instantiate env t in
case L.find (flip S.member (fv t')) vars of
Nothing -> return t'
Just var ->
fail $ shows pos $ showString "The variable " $
showsTerm vars var " is not mapped in a term"
where
fv t = foldVars (flip S.insert) S.empty t
-- Instantiate formula and ensure all the free variables have been mapped.
transForm :: (Algebra t p g s e c, Monad m) => Pos -> [t] -> e ->
Formula t p g s e c -> m (Formula t p g s e c)
transForm pos vars env f =
let f' = finstantiate env f in
case L.find (flip S.member (freeVars f')) vars of
Nothing -> return f'
Just var ->
fail $ shows pos $ showString "The variable " $
showsTerm vars var " is not mapped in a formula"
displayAnnos :: Algebra t p g s e c => [t] ->
[Annotations t p g s e c] -> SExpr ()
displayAnnos vars annos =
L () (S () annotationsKey : table)
where
table = [ displayObl vars ((s, p), t, f) |
(s, Just (t, fs)) <- zip [0..] annos,
(p, f) <- zip [0..] fs,
not (truth f) ]
-- Graphs as adjacency lists
adj :: Strands -> [Pair] -> Node -> [Node]
adj strands precedes (s, p) =
[ strand s h | (s, h) <- zip [0..] strands ] !! s !! p
where
strand :: Int -> Int -> [[Node]]
strand s h = [ entry (s, p) | p <- nats h ]
entry :: Node -> [Node]
entry n = enrich n [ n0 | (n0, n1) <- precedes, n1 == n ]
-- add strand succession edges
enrich (s, p) ns
| p > 0 = (s, p - 1) : ns
| otherwise = ns
-- Transitive closure
close :: Strands -> [Pair] -> [Pair]
close strands precedes =
loop prec False prec
where
prec = successors strands ++ precedes
loop prec False [] = prec
loop prec True [] =
loop prec False prec -- restart loop
loop prec repeat ((n0, n1) : pairs) =
inner prec repeat pairs [(n, n1) | n <- adj strands precedes n0]
inner prec repeat pairs [] =
loop prec repeat pairs
inner prec repeat pairs (p : rest)
| elem p prec = inner prec repeat pairs rest
| otherwise = inner (p : prec) True pairs rest
-- Filter pairs that don't effect annotations.
orient :: [Trace] -> [Pair] -> [Pair]
orient traces precedes =
L.nub $ L.filter pred precedes
where
pred (n0, n1) = out traces n0 && not (out traces n1)
out :: [Trace] -> Node -> Bool
out traces (s, p) = traces !! s !! p
successors :: Strands -> [Pair]
successors strands =
[((s, p), (s, p + 1)) | (s, n) <- zip [0..] strands, p <- nats (n - 1)]
before :: (Algebra t p g s e c, Monad m) => Pos ->
Preskel t p g s e c -> m [(Node, [Node])]
before pos k =
case isAcyclic graph (zip [0..] (replicate (length strands) 0)) of
False -> fail (shows pos "Cycle found")
True -> return $ alist (traces k) $ orient (traces k) closure
where
closure = close strands (orderings k)
strands = map height (insts k)
graph = adj strands (orderings k)
-- Computes all transmission nodes before a reception node
alist :: [Trace] -> [Pair] -> [(Node, [Node])]
alist traces precedes =
[ node (s, p) | (s, t) <- zip [0..] traces, (p, d) <- zip [0..] t, not d ]
where
node n = (n, L.sort (depends n))
depends n = [ n0 | (n0, n1) <- precedes, n == n1 ]
type Obligation t p g s e c = (Node, t, Formula t p g s e c)
obligations :: (Algebra t p g s e c, Monad m) => Pos ->
Preskel t p g s e c -> [Annotations t p g s e c] ->
m [Obligation t p g s e c]
obligations pos k annos =
do
depends <- before pos k
let obls = map (obligation annos) depends
-- filter out trivial obligations
return [ obl | Just obl@(_, _, f) <- obls, not (truth f) ]
obligation :: Algebra t p g s e c => [Annotations t p g s e c] ->
(Node, [Node]) -> Maybe (Obligation t p g s e c)
obligation annos (n@(s, p), ns) =
do
(t, fs) <- annos !! s -- t relies on (fs !! p)
let guar = [ if t == t' then f else says t' f | -- Believe t
(s, p) <- ns, -- For each predecessor
let Just (t', fs) = annos !! s, -- t' is the speaker
let f = fs !! p ] -- f is the guarantee
return (n, t, implies guar (fs !! p))
displayObls :: Algebra t p g s e c => [t] ->
[Obligation t p g s e c] -> SExpr ()
displayObls vars obls =
L () (S () obligationsKey : map (displayObl vars) obls)
displayObl :: Algebra t p g s e c => [t] ->
Obligation t p g s e c -> SExpr ()
displayObl vars (n, t, f) =
L () [displayNode n, displayTerm ctx t, displayFormula vars f]
where
ctx = varsContext vars
displayNode :: Node -> SExpr ()
displayNode (s, p) = L () [N () s, N () p]
addPos :: Pos -> SExpr a -> SExpr Pos
addPos pos (S _ s) = S pos s
addPos pos (Q _ s) = Q pos s
addPos pos (N _ n) = N pos n
addPos pos (L _ l) = L pos (map (addPos pos) l)
-- Association lists
-- Lookup value in alist, appending values with the same key
assoc :: String -> [SExpr a] -> [SExpr a]
assoc key alist =
concat [ rest | L _ (S _ head : rest) <- alist, key == head ]
keyPred :: String -> SExpr a -> Bool
keyPred key (L _ (S _ head : _)) = key == head
keyPred _ _ = False
hasKey :: String -> [SExpr a] -> Bool
hasKey key alist = any (keyPred key) alist
strip :: String -> [SExpr a] -> [SExpr a]
strip key alist = filter (not . keyPred key) alist
-- the key used in protocols and preskeletons for annotations
annotationsKey :: String
annotationsKey = "annotations"
-- The key used in preskeletons for obligations
obligationsKey :: String
obligationsKey = "obligations"
-- The key used in preskeletons for communication orderings
precedesKey :: String
precedesKey = "precedes"
-- The key used in preskeletons for traces
tracesKey :: String
tracesKey = "traces"