cpsa-2.5.0: src/CPSA/Lib/Characteristic.hs
-- Makes the characteristic skeleton of a security goal
-- Copyright (c) 2015 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.Characteristic (Conj, characteristic) where
import Control.Monad
import qualified Data.List as L
import CPSA.Lib.Utilities
import CPSA.Lib.SExpr
import CPSA.Lib.Algebra
import CPSA.Lib.Protocol
import CPSA.Lib.Goal
import CPSA.Lib.Strand
{--
import System.IO.Unsafe
z :: Show a => a -> b -> b
z x y = unsafePerformIO (print x >> return y)
--}
type Conj t = [(Pos, AForm t)]
-- Entry point. Takes a position, a protocol, a variable generator, a
-- goal, and a skeleton comment and makes a skeleton or fails. This
-- function extracts the anecedent and univesally quantified variable.
characteristic :: (Algebra t p g s e c, Monad m) => Pos -> Prot t g ->
[Goal t] -> g -> Conj t -> [SExpr ()] -> m (Preskel t g s e)
characteristic pos prot goals g antec comment =
equalsForm pos prot goals g antec comment
-- Checks for equals in an antecedent and fails if it finds one. One
-- could use unification to solve the equality, and then apply the
-- result to the remaining parts of the formula.
equalsForm :: (Algebra t p g s e c, Monad m) => Pos -> Prot t g ->
[Goal t] -> g -> Conj t -> [SExpr ()] -> m (Preskel t g s e)
equalsForm pos _ _ _ as _ | any isEquals as =
fail (shows pos "Equals not allowed in antecedent")
equalsForm pos prot goals g as comment =
splitForm pos prot goals g as comment
isEquals :: (Pos, AForm t) -> Bool
isEquals (_, Equals _ _) = True
isEquals _ = False
-- Split the formula into instance formulas and skeleton formulas.
-- The instance formulas are used to generate the skeleton's
-- instances, and the skeleton formulas generate the rest. Make the
-- instances, and then make the rest.
splitForm :: (Algebra t p g s e c, Monad m) => Pos -> Prot t g ->
[Goal t] -> g -> Conj t -> [SExpr ()] -> m (Preskel t g s e)
splitForm pos prot goals g as comment =
do
(nmap, g, insts) <- mkInsts g is
mkSkel pos prot goals nmap g insts ks comment
where -- is is the instance formulas and
(is, ks) = L.partition instForm as -- ks is the skeleton formulas
-- Instance formulas are role predicates, parameter predicates, and
-- strand prec.
instForm :: (Pos, AForm t) -> Bool
instForm (_, RolePred _ _ _) = True
instForm (_, ParamPred _ _ _ _) = True
instForm (_, StrPrec _ _) = True
instForm _ = False
-- Make the instances from the instance predicates
mkInsts :: (Algebra t p g s e c, Monad m) => g -> Conj t ->
m ([(t, Node)], g, [Instance t e])
mkInsts g as =
do
nri <- nodeRoleIndex as -- Compute index and role of each node
nss <- binNodes nri as -- Collect nodes on the same strand
(g, insts) <- foldInsts g as nri nss -- Construct instances
return (nodeMap nri nss, g, insts) -- Construct node map for later use
type RoleIndex t = (Role t, Int)
-- Computes a map from nodes to role-index pairs
nodeRoleIndex :: (Eq t, Monad m) => Conj t -> m [(t, RoleIndex t)]
nodeRoleIndex as =
foldM f [] as
where
f nri (pos, RolePred r i n) =
case lookup n nri of
Nothing -> return ((n, (r, i)) : nri)
Just _ -> fail (shows pos
"Node occurs in more than one role predicate")
f nri _ = return nri
-- Use this lookup when lookup must succeed.
nriLookup :: Eq t => t -> [(t, RoleIndex t)] -> RoleIndex t
nriLookup n nri =
case lookup n nri of
Just ri -> ri
Nothing -> error "Characteristic.nriLookup: Bad lookup"
--- Use str-prec to collect the nodes on the same strand. Check to
--- make sure the role associated with nodes is the same.
binNodes :: (Eq t, Monad m) => [(t, RoleIndex t)] -> Conj t -> m [[t]]
binNodes nri as =
foldM f (map (\(x, _) -> [x]) nri) as
where
f nss (pos, StrPrec n n')
| i >= i' || rname r /= rname r' =
fail (shows pos "Bad str-prec")
| otherwise = return $ merge n n' nss
where
(r, i) = nriLookup n nri
(r', i') = nriLookup n' nri
f nss _ = return nss
-- Merge two sets of nodes and delete the old sets
merge :: Eq t => t -> t -> [[t]] -> [[t]]
merge n n' nss =
(ns ++ ns') : L.delete ns (L.delete ns' nss)
where
ns = findl n nss
ns' = findl n' nss
-- Find a set containing node n
findl :: Eq t => t -> [[t]] -> [t]
findl n nss =
case L.find (elem n) nss of
Just ns -> ns
Nothing -> error "Characteristic.findl: cannot find a node"
-- Construct instances
foldInsts :: (Algebra t p g s e c, Monad m) => g -> Conj t ->
[(t, RoleIndex t)] -> [[t]] -> m (g, [Instance t e])
foldInsts g _ _ [] = return (g, [])
foldInsts g as nri (ns : nss) =
do
(g, inst) <- mkInst g as nri ns
(g, insts) <- foldInsts g as nri nss
return (g, inst : insts)
-- Construct an instance by extracting maplets from the parameter
-- predicates with nodes associated with the strand.
mkInst :: (Algebra t p g s e c, Monad m) => g -> Conj t ->
[(t, RoleIndex t)] -> [t] -> m (g, Instance t e)
mkInst _ _ _ [] = error "Characteristic.mkInst: no nodes"
mkInst g as nri (n : ns)
| h < 1 || h > length (rtrace r) = -- Checked by the the loader
error "Character.mkInst: Bad height"
| otherwise =
do
(g, env) <- foldM (mkMaplet r (n : ns)) (g, emptyEnv) as
return (mkInstance g r env h)
where
(r, i) = nriLookup n nri
-- The height (1 + max index)
h = 1 + foldr f i ns
f n i = max i (snd $ nriLookup n nri)
-- Add match from a maplet
mkMaplet :: (Algebra t p g s e c, Monad m) => Role t ->
[t] -> (g, e) -> (Pos, AForm t) -> m (g, e)
mkMaplet role ns env (pos, ParamPred r v n t)
| elem n ns =
if rname role == rname r then -- Ensure role match the one
case match v t env of -- used to create instance
env : _ -> return env
[] -> fail (shows pos "Domain does not match range")
else
fail (shows pos
"Role in parameter pred differs from role position pred")
mkMaplet _ _ env _ = return env
-- Generate a map from node variables to node constants.
nodeMap :: Eq t => [(t, RoleIndex t)] -> [[t]] -> [(t, Node)]
nodeMap nri nss =
[ (n, (z, i)) |
(z, ns) <- zip [0..] nss,
n <- ns,
let (_, i) = nriLookup n nri ]
-- Use this lookup when lookup must succeed, that is when loader makes
-- the check.
nMapLookup :: Eq t => t -> [(t, Node)] -> Node
nMapLookup n nmap =
case lookup n nmap of
Just n -> n
Nothing -> error "Characteristic.nMapLookup: Bad lookup"
-- Create a skeleton given a list of instances
mkSkel :: (Algebra t p g s e c, Monad m) => Pos -> Prot t g ->
[Goal t] -> [(t, Node)] -> g -> [Instance t e] ->
Conj t -> [SExpr ()] -> m (Preskel t g s e)
mkSkel pos p goals nmap g insts as comment =
do
let o = foldr (mkPrec nmap) [] as
let nr = foldr mkNon [] as
let ar = foldr mkPnon [] as
let ur = foldr mkUniq [] as
let (nr', ar', ur') = foldl addInstOrigs (nr, ar, ur) insts
let prios = []
let k = mkPreskel g p goals insts o nr' ar' ur' prios comment
mapM_ (checkUniqAt nmap k) as
case termsWellFormed $ nr' ++ ar' ++ ur' ++ kterms k of
False -> fail (shows pos "Terms in skeleton not well formed")
True -> return ()
case verbosePreskelWellFormed k of
Right () -> return k
Left msg -> fail $ shows pos
$ showString "Skeleton not well formed: " msg
addInstOrigs :: Algebra t p g s e c => ([t], [t], [t]) ->
Instance t e -> ([t], [t], [t])
addInstOrigs (nr, ar, ur) i =
(foldl (flip adjoin) nr $ inheritRnon i,
foldl (flip adjoin) ar $ inheritRpnon i,
foldl (flip adjoin) ur $ inheritRunique i)
mkPrec :: Eq t => [(t, (Int, Int))] ->
(Pos, AForm t) -> [Pair] -> [Pair]
mkPrec nmap (_, Prec n n') o =
(nMapLookup n nmap, nMapLookup n' nmap) : o
mkPrec _ _ o = o
mkNon :: (Pos, AForm t) -> [t] -> [t]
mkNon (_, Non t) ts = t : ts
mkNon _ ts = ts
mkPnon :: (Pos, AForm t) -> [t] -> [t]
mkPnon (_, Pnon t) ts = t : ts
mkPnon _ ts = ts
mkUniq :: (Pos, AForm t) -> [t] -> [t]
mkUniq (_, Uniq t) ts = t : ts
mkUniq (_, UniqAt t _) ts = t : ts
mkUniq _ ts = ts
checkUniqAt :: (Algebra t p g s e c, Monad m) => [(t, Node)] ->
Preskel t g s e -> (Pos, AForm t) -> m ()
checkUniqAt nmap k (pos, UniqAt t n) =
case lookup t $ korig k of
Nothing -> fail (shows pos "Atom not unique at node")
Just ns
| elem (nMapLookup n nmap) ns -> return ()
| otherwise -> fail (shows pos "Atom not unique at node")
checkUniqAt _ _ _ = return ()