-- Loads protocols and preskeletons from S-expressions. -- 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.Loader (loadSExprs) where import Control.Monad import qualified Data.List as L import qualified Data.Foldable as F import Data.Maybe (isJust) import CPSA.Lib.Utilities import CPSA.Lib.ReturnFail import CPSA.Lib.SExpr import CPSA.Signature (Sig, loadSig) import CPSA.Algebra import CPSA.Channel import CPSA.Protocol import CPSA.Strand import CPSA.Characteristic import CPSA.LoadFormulas import CPSA.GenRules {-- import System.IO.Unsafe z :: Show a => a -> b -> b z x y = unsafePerformIO (print x >> return y) zz :: Show a => a -> a zz x = z x x --} -- Load protocols and preskeletons from a list of S-expressions, and -- then return a list of preskeletons. The name of the algebra is -- nom, and its variable generator is provided. loadSExprs :: MonadFail m => Sig -> String -> Gen -> [SExpr Pos] -> m [Preskel] loadSExprs sig nom origin xs = do (_, ks) <- foldM (loadSExpr sig nom origin) ([], []) xs return (reverse ks) loadSExpr :: MonadFail m => Sig -> String -> Gen -> ([Prot], [Preskel]) -> SExpr Pos -> m ([Prot], [Preskel]) loadSExpr sig nom origin (ps, ks) (L pos (S _ "defprotocol" : xs)) = do p <- loadProt sig nom origin pos xs return (p : ps, ks) loadSExpr _ _ _ (ps, ks) (L pos (S _ "defskeleton" : xs)) = do k <- findPreskel pos ps xs return (ps, k : ks) loadSExpr _ _ _ (ps, ks) (L pos (S _ "defgoal" : xs)) = do k <- findGoal pos ps xs return (ps, k : ks) loadSExpr _ _ _ (ps, ks) (L _ (S _ "comment" : _)) = return (ps, ks) loadSExpr _ _ _ (ps, ks) (L _ (S _ "herald" : _)) = return (ps, ks) loadSExpr _ _ _ _ x = fail (shows (annotation x) "Malformed input") -- load a protocol loadProt :: MonadFail m => Sig -> String -> Gen -> Pos -> [SExpr Pos] -> m Prot loadProt sig nom origin pos (S _ name : S _ alg : x : xs) | alg /= nom = fail (shows pos $ "Expecting terms in algebra " ++ nom) | otherwise = do sig <- loadLang pos sig xs (gen, rolesAndPreRules, rest) <- loadRoles sig origin (x : xs) (gen', r) <- mkListenerRole sig pos gen let rs = map fst rolesAndPreRules -- let _ = zz (showRoleGenStates rolesAndPreRules) -- let (gen, rls) = initRules sig (any hasLocn rs) gen' stateRules nonStateRules -- Fake protocol is used only for loading user defined rules let fakeProt = mkProt name alg gen sig rs r [] -- was rls [] -- user-written rules [] -- loader-generated rules was rls [] (gen, forgettableRls, memorableRls) <- initRules sig gen' fakeProt rolesAndPreRules (gen, newRls, comment) <- loadRules sig fakeProt gen rest -- Check for duplicate role names (validate (mkProt name alg gen sig rs r (newRls ++ forgettableRls ++ memorableRls) (newRls ++ memorableRls) -- user-written rules forgettableRls -- loader-regenerated rules comment) rs) where validate prot [] = return prot validate prot (r : rs) = case L.find (\r' -> rname r == rname r') rs of Nothing -> validate prot rs Just _ -> let msg = "Duplicate role " ++ rname r ++ " in protocol " ++ name in fail (shows pos msg) loadProt _ _ _ pos _ = fail (shows pos "Malformed protocol") -- Optionally load a lang field in a protocol. loadLang :: MonadFail m => Pos -> Sig -> [SExpr Pos] -> m Sig loadLang pos _ xs | hasKey "lang" xs = loadSig pos (assoc "lang" xs) loadLang _ sig _ | otherwise = return sig loadRoles :: MonadFail m => Sig -> Gen -> [SExpr Pos] -> m (Gen, [(Role,PreRules)], [SExpr Pos]) loadRoles sig gen (L pos (S _ "defrole" : x) : xs) = do (gen, r, pr) <- loadRole sig gen pos x (gen, rolesAndPreRules, comment) <- loadRoles sig gen xs return (gen, (r,pr) : rolesAndPreRules, comment) loadRoles _ gen comment = return (gen, [], comment) loadRole :: MonadFail m => Sig -> Gen -> Pos -> [SExpr Pos] -> m (Gen, Role, PreRules) loadRole sig gen pos (S _ name : L _ (S _ "vars" : vars) : L _ (S _ "trace" : evt : c) : rest) = do (gen, vars) <- loadVars sig gen vars (gen, vars, pt_u, pr_t, c) <- -- critical section indices computed below loadTrace sig gen vars (evt : c) n <- loadPosBaseTerms sig vars (assoc "non-orig" rest) a <- loadPosBaseTerms sig vars (assoc "pen-non-orig" rest) u <- loadBaseTerms sig vars (assoc "uniq-orig" rest) g <- loadBaseTerms sig vars (assoc "uniq-gen" rest) b <- mapM (loadAbsent sig vars) (assoc "absent" rest) d <- loadBaseTerms sig vars (assoc "conf" rest) h <- loadBaseTerms sig vars (assoc "auth" rest) cs <- loadCritSecs (assoc "critical-sections" rest) let genstates = (assoc "gen-st" rest) let facts = (assoc "facts" rest) let assumes = (assoc "assume" rest) let keys = ["non-orig", "pen-non-orig", "uniq-orig", "uniq-gen", "absent", "conf", "auth"] comment <- alist keys rest let reverseSearch = hasKey "reverse-search" rest let ts = tterms c let bs = concatMap (\(x, y) -> [x, y]) b case termsWellFormed (map snd n ++ map snd a ++ u ++ g ++ bs ++ ts) of False -> fail (shows pos "Terms in role not well formed") True -> return () case L.all isChan (d ++ h) of False -> fail (shows pos "Bad channel in role") True -> return () -- Drop unused variable declarations let f v = elem v (varsInTerms ts) || elem v (tchans c) let vs = L.filter f vars -- Drop rnons that refer to unused variable declarations let ns = L.filter (varsSeen vs . snd) n -- Drop rpnons that refer to unused variable declarations let as = L.filter (varsSeen vs . snd) a -- Drop runiques that refer to unused variable declarations let us = L.filter (varsSeen vs) (u ++ pt_u) let gs = L.filter (varsSeen vs) g prios <- mapM (loadRolePriority (length c)) (assoc "priority" rest) let stateSegs = stateSegments c case all (flip checkCs stateSegs) cs of False -> fail (shows pos "Critical sections in role not within state segments") True -> return () let r = mkRole name vs c ns as us gs b d h comment prios reverseSearch let pr = pr_t { preruleCs = stateSegs, preruleTrans = transitionIndices c, preruleFacts = facts, preruleGensts = genstates, preruleAssumes = assumes } -- let (gen', transRls) = transRules sig gen r (transitionIndices c) -- -- :: Gen -> Role -> [Int] -> (Gen, [Rule]) -- let (gen'', csRls) = csRules sig gen' r cs -- let (gen''', gsRls) = genStateRls sig gen'' r genstates -- let (genFour, factRls) = genFactRls sig gen''' r facts case roleWellFormed r of Return () -> return (gen, r, pr -- (gsRls ++ csRls ++ transRls), -- factRls ) Fail msg -> fail (shows pos $ showString "Role not well formed: " msg) loadRole _ _ pos _ = fail (shows pos "Malformed role") loadRolePriority :: MonadFail m => Int -> SExpr Pos -> m (Int, Int) loadRolePriority n (L _ [N _ i, N _ p]) | 0 <= i && i < n = return (i, p) loadRolePriority _ x = fail (shows (annotation x) "Malformed priority") -- Are the vars in t a subset of ones in t. varsSeen :: [Term] -> Term -> Bool varsSeen vs t = all (flip elem vs) (addVars [] t) data PreRules = PreRules { preruleCs :: [(Int,Int)], preruleTrans :: [(Int,Int)], preruleFacts :: [SExpr Pos], preruleGensts :: [SExpr Pos], preruleAssumes :: [SExpr Pos], preruleRelies :: [(Int, SExpr Pos)], preruleGuars :: [(Int, SExpr Pos)], preruleCheqs :: [(Int, Pos, Term, Term)] } emptyPreRules :: PreRules emptyPreRules = PreRules { preruleCs = [], preruleTrans = [], preruleFacts = [], preruleGensts = [], preruleAssumes = [], preruleRelies = [], preruleGuars = [], preruleCheqs = []} preRulesAddRely :: PreRules -> (Int, SExpr Pos) -> PreRules preRulesAddRely pr new = pr { preruleRelies = new : preruleRelies pr } preRulesAddGuar :: PreRules -> (Int, SExpr Pos) -> PreRules preRulesAddGuar pr new = pr { preruleGuars = new : preruleGuars pr } preRulesAddCheq :: PreRules -> (Int, Pos, Term, Term) -> PreRules preRulesAddCheq pr new = pr { preruleCheqs = new : preruleCheqs pr } -- A role is well formed if all non-base variables are receive bound, -- each atom declared to be uniquely-originating originates in -- the trace, and every variable that occurs in each atom -- declared to be non-originating occurs in some term in the trace, -- and the atom must never be carried by any term in the trace. roleWellFormed :: MonadFail m => Role -> m () roleWellFormed role = do failwith "a variable in non-orig is not in trace" $ varSubset (map snd $ rnon role) terms failwith "a variable in pen-non-orig is not in trace" $ varSubset (map snd $ rpnon role) terms mapM_ nonCheck $ rnon role mapM_ lenCheck $ rnon role mapM_ lenCheck $ rpnon role mapM_ uniqueCheck $ runique role mapM_ uniqgenCheck $ runiqgen role mapM_ origVarCheck $ rvars role failwith "role trace is a prefix of a listener" $ notListenerPrefix $ rtrace role failwith "role trace has stor with no previous load" $ balancedStores $ rtrace role where terms = tterms (rtrace role) nonCheck (_, t) = failwith (showString "non-orig " $ showst t " carried") $ all (not . carriedBy t) terms lenCheck (Nothing, _) = return () lenCheck (Just len, _) | len >= length (rtrace role) = fail $ showString "invalid position " $ show len lenCheck (Just len, t) | otherwise = case usedPos t (rtrace role) of Just p | p <= len -> return () Just _ -> fail $ showst t $ showString " appears after position " $ show len Nothing -> fail msg where msg = "no used position for non-originating atom " ++ showst t "" uniqueCheck t = failwith (showString "uniq-orig " $ showst t " doesn't originate") $ originates t (rtrace role) uniqgenCheck t = failwith (showString "uniq-gen " $ showst t " doesn't generate") $ generates t (rtrace role) origVarCheck v = failwith (showString "variable " $ showst v " not acquired") $ not (isAcquiredVar v) || isJust (acquiredPos v (rtrace role)) -- noBareStore :: Trace -> Bool -- noBareStore c = -- check c Nothing -- where -- check [] _ = True -- check ((In (ChMsg ch _)) : c') _ -- | isLocn ch = check c' (Just ch) -- | otherwise = check c' Nothing -- check ((Out (ChMsg ch _)) : c') (Just ch') -- | ch == ch' = check c' Nothing -- | isLocn ch = False -- | otherwise = check c' Nothing -- check ((Out (ChMsg ch _)) : c') Nothing -- | isLocn ch = False -- | otherwise = check c' Nothing -- check (_ : c') _ = check c' Nothing balancedStores :: Trace -> Bool balancedStores c = check c [] where check [] _ = True check ((In (ChMsg ch _)) : c') loads | isLocn ch = check c' (ch : loads) | otherwise = check c' [] check ((Out (ChMsg ch _)) : c') loads | ch `elem` loads = check c' loads | isLocn ch = False | otherwise = check c' [] check (_ : c') _ = check c' [] -- Given a trace, return a list of pairs of indices. The first member -- of each pair is the index of a state event. If the state event is -- a stor, then the second member is equal to the first. If the state -- event is a load, then there is a matching stor in the state -- segment, and the second member is its index. transitionIndices :: Trace -> [(Int, Int)] transitionIndices c = reverse $ loop [] 0 c where loop so_far _ [] = so_far loop so_far i ((Out (ChMsg ch _)) : c') | isLocn ch = loop ((i,i) : so_far) (i+1) c' | otherwise = loop so_far (i+1) c' loop so_far i ((In (ChMsg ch _)) : c') | isLocn ch = case subseqSend (i+1) ch c' of Just j -> loop ((i,j) : so_far) (i+1) c' Nothing -> loop so_far (i+1) c' | otherwise = loop so_far (i+1) c' loop so_far i (_ : c') = loop so_far (i+1) c' subseqSend _ _ [] = Nothing subseqSend j ch ((In (ChMsg _ _)) : c') = subseqSend (j+1) ch c' subseqSend j ch ((Out (ChMsg ch' _)) : c') | ch == ch' = Just j | isLocn ch' = subseqSend (j+1) ch c' | otherwise = Nothing subseqSend _ _ (_ : _) = Nothing loadCritSecs :: MonadFail m => [SExpr Pos] -> m [(Int, Int)] loadCritSecs [] = return [] loadCritSecs (L pos [(N _ i), (N _ j)] : rest) | j [(Int,Int)] stateSegments c = findSegments [] 0 c where findSegments soFar _ [] = soFar findSegments soFar i ((Out _) : c) = findSegments soFar (i+1) c findSegments soFar i ((In (Plain _)) : c) = findSegments soFar (i+1) c findSegments soFar i ((In (ChMsg ch _)) : c) | isLocn ch = findEnd soFar i (i+1) c False -- the flag False means that no Outs have yet been observed | otherwise = findSegments soFar (i+1) c -- findEnd scans for the end of the segment starting at start. -- i is always the index of the start of c in the full trace. -- The boolean flag is False until Outs have been observed. findEnd soFar start i [] _ = (start,(i-1)) : soFar findEnd soFar start i ((In (Plain _)) : c) _ = findSegments ((start,(i-1)) : soFar) (i+1) c findEnd soFar start i ((Out (Plain _)) : c) _ = findSegments ((start,(i-1)) : soFar) (i+1) c findEnd soFar start i c@((In (ChMsg _ _)) : _) True = findSegments ((start,(i-1)) : soFar) i c -- in th eprevious case we save the start of c for findSegments -- to decide what to do. findEnd soFar start i ((In (ChMsg ch _)) : c) False | isLocn ch = findEnd soFar start(i+1) c False | otherwise = findSegments ((start,(i-1)) : soFar) (i+1) c findEnd soFar start i ((Out (ChMsg ch _)) : c) _ | isLocn ch = findEnd soFar start (i+1) c True | otherwise = findSegments ((start,(i-1)) : soFar) (i+1) c checkCs :: (Int,Int) -> [(Int,Int)] -> Bool checkCs (i,j) = any (\(start,end) -> start <= i && j <= end) failwith :: MonadFail m => String -> Bool -> m () failwith msg test = case test of True -> return () False -> fail msg showst :: Term -> ShowS showst t = shows $ displayTerm (addToContext emptyContext [t]) t -- This is the only place a role is generated with an empty name. -- This is what marks a strand as a listener. mkListenerRole :: MonadFail m => Sig -> Pos -> Gen -> m (Gen, Role) mkListenerRole sig pos g = do (g, xs) <- loadVars sig g [L pos [S pos "x", S pos "mesg"]] case xs of [x] -> return (g, mkRole "" [x] [In $ Plain x, Out $ Plain x] [] [] [] [] [] [] [] [] [] False) _ -> fail (shows pos "mkListenerRole: expecting one variable") -- Ensure a trace is not a prefix of a listener notListenerPrefix :: Trace -> Bool notListenerPrefix (In t : Out t' : _) | t == t' = False notListenerPrefix _ = True -- Are there any locations used in the role? hasLocn :: Role -> Bool hasLocn rl = any isLocn (tchans (rtrace rl)) initPreRuleCS :: MonadFail m => Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule]) initPreRuleCS sig gen _ rl pr = return (csRules sig gen rl (preruleCs pr)) initPreRulesTrans :: MonadFail m => Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule]) initPreRulesTrans sig gen _ rl pr = return (transRls sig gen rl (preruleTrans pr)) initPreRulesFacts :: MonadFail m => Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule]) initPreRulesFacts sig gen _ rl pr = do facts <- loadFactList sig (rvars rl) (preruleFacts pr) return (genFactRls sig gen rl facts) initPreRulesGensts :: MonadFail m => Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule]) initPreRulesGensts sig gen _ rl pr = do ts <- loadTerms sig (rvars rl) (preruleGensts pr) return (genStateRls sig gen rl ts) initPreRulesAssumes :: MonadFail m => Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule]) initPreRulesAssumes sig gen prot rl pr = do (g,rules,_) <- F.foldrM (\sexpr (g,rules,n) -> do (g',varConjs) <- loadConclusion sig (annotation sexpr) prot g (rvars rl) sexpr let (g'',newRule) = genOneAssumeRl sig g' rl n varConjs return (g'', newRule : rules, (n+1))) (gen,[],0) (preruleAssumes pr) return (g,rules) initPreRulesRelies :: MonadFail m => Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule]) initPreRulesRelies sig gen prot rl pr = initRelyGuars sig gen prot rl "rely" (preruleRelies pr) initPreRulesGuars :: MonadFail m => Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule]) initPreRulesGuars sig gen prot rl pr = initRelyGuars sig gen prot rl "guar" (preruleGuars pr) initRelyGuars :: MonadFail m => Sig -> Gen -> Prot -> Role -> String -> [(Int, SExpr Pos)] -> m (Gen, [Rule]) initRelyGuars sig gen prot rl kind = F.foldrM (\(ht,sexpr) (g,rules) -> do (g',varConjs) <- loadConclusion sig (annotation sexpr) prot g (rvars rl) sexpr () <- varsUsedBy rl (freeVarsInConjLists varConjs) ht (annotation sexpr) let (g'',newRule) = genOneRelyGuarRl sig g' rl ht kind varConjs return (g'', newRule : rules)) (gen,[]) initPreRulesCheqs :: MonadFail m => Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule]) initPreRulesCheqs sig gen _ rl pr = F.foldrM (\(ht, pos, v, t) (g,rules) -> do () <- varsUsedBy rl (varsInTerms [t]) ht pos let (g',newRule) = genOneRelyGuarRl sig g rl ht "cheq" [([],[(pos, Equals v t)])] -- stipulate v=t return (g', newRule : rules)) (gen,[]) (preruleCheqs pr) varsUsedBy :: MonadFail m => Role -> [Term] -> Int -> Pos -> m () varsUsedBy rl vars bound pos = case varsUsedHeight rl vars of Missing v -> fail ((show pos) ++ " var " ++ (show (varName v)) ++ " not bound by height " ++ (show bound)) FoundAt ht -> if ht <= bound then return () else fail ((show pos) ++ " variables not bound until height " ++ (show ht)) iterPreRules :: MonadFail m => (Sig -> Gen -> Prot -> Role -> PreRules -> m (Gen, [Rule])) -> Sig -> Gen -> Prot -> [(Role,PreRules)] -> m (Gen, [Rule]) iterPreRules f sig gen prot rlPreRules = F.foldrM (\(rl,prs) (g,rules) -> do (g',newRules) <- f sig g prot rl prs return (g', newRules ++ rules)) (gen,[]) rlPreRules initRules :: MonadFail m => Sig -> Gen -> Prot -> [(Role,PreRules)] -> m (Gen, [Rule], [Rule]) initRules sig g prot prs = -- Must generate neqRules, factRules, assumeRules, relyRules, -- guarRules, cheqRules, and, -- if b is true, the state rules: -- transRules, genStateRules, csRules, and the omnipresent state -- rules for -- scissors, cake, (inv) shears, and interruptions -- let (g',neqs) = neqRules sig g in -- let (g'', trans) = transRules g' in let anyState = any (\(rl,_) -> hasLocn rl) prs in do let (g',neqs) = neqRules sig g let (g,fixedStateRls) = if anyState then foldr (\f (g,rules) -> let (g',r) = f g in (g',r : rules)) (g',[]) [scissorsRule sig, cakeRule sig, uninterruptibleRule sig, shearsRule sig, invShearsRule sig] else (g',[]) (g,fcRls) <- iterPreRules initPreRulesFacts sig g prot prs (g,asRls) <- iterPreRules initPreRulesAssumes sig g prot prs (g,rlRls) <- iterPreRules initPreRulesRelies sig g prot prs (g,grRls) <- iterPreRules initPreRulesGuars sig g prot prs (g,cqRls) <- iterPreRules initPreRulesCheqs sig g prot prs -- Now here are the ones for states (g,trRls) <- iterPreRules initPreRulesTrans sig g prot prs (g,csRls) <- iterPreRules initPreRuleCS sig g prot prs (g,gsRls) <- iterPreRules initPreRulesGensts sig g prot prs return (g, neqs ++ fixedStateRls ++ fcRls ++ asRls ++ trRls ++ csRls ++ gsRls, rlRls ++ grRls ++ cqRls) {-- showRoleGenStates :: [(Role,PreRules)] -> [(String,String)] showRoleGenStates [] = [] showRoleGenStates ((role, pr) : rest) = (rname role, concatMap show $ map snd $ preruleGuars pr) : showRoleGenStates rest --} loadRules :: MonadFail m => Sig -> Prot -> Gen -> [SExpr Pos] -> m (Gen, [Rule], [SExpr ()]) loadRules sig prot g (L pos (S _ "defrule" : x) : xs) = do (g, r) <- loadRule sig prot g pos x (g, rs, comment) <- loadRules sig prot g xs return (g, r : rs, comment) loadRules _ _ _ (L pos (S _ "defrole" : S _ name : _) : _) = fail (shows pos ("defrole " ++ name ++ " misplaced")) loadRules _ _ g xs = do badKey ["defrole", "defrule"] xs comment <- alist [] xs -- Ensure remaining is an alist return (g, [], comment) loadRule :: MonadFail m => Sig -> Prot -> Gen -> Pos -> [SExpr Pos] -> m (Gen, Rule) loadRule sig prot g pos (S _ name : x : xs) = do (g, goal, _) <- loadSentence sig UnusedVars pos prot g x comment <- alist [] xs -- Ensure remaining is an alist return (g, Rule { rlname = name, rlgoal = goal, rlcomment = comment }) loadRule _ _ _ pos _ = fail (shows pos "Malformed rule") -- Association lists -- Make an association list into a comment. The first argument is the -- set of keys of key-value pairs to be dropped from the comment. alist :: MonadFail m => [String] -> [SExpr Pos] -> m [SExpr ()] alist _ [] = return [] alist keys (a@(L _ (S _ key : _)) : xs) | elem key keys = alist keys xs | otherwise = do xs <- alist keys xs return $ strip a : xs alist _ xs = fail (shows (annotation $ head xs) "Malformed association list") -- Strip positions from an S-expression strip :: SExpr a -> SExpr () strip (S _ s) = S () s strip (Q _ s) = Q () s strip (N _ n) = N () n strip (L _ l) = L () (map strip l) -- 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 ] -- See if alist has a key hasKey :: String -> [SExpr a] -> Bool hasKey key alist = any f alist where f (L _ (S _ head : _)) = head == key f _ = False -- Complain if alist has a bad key badKey :: MonadFail m => [String] -> [SExpr Pos] -> m () badKey keys (L _ (S pos key : _) : xs) | elem key keys = fail (shows pos (key ++ " declaration too late in enclosing form")) | otherwise = badKey keys xs badKey _ _ = return () loadTrace :: MonadFail m => Sig -> Gen -> [Term] -> [SExpr Pos] -> m (Gen, [Term], [Term], PreRules, Trace) loadTrace sig gen vars xs = loadTraceLoop gen [] [] emptyPreRules [] xs where loadTraceLoop :: MonadFail m => Gen -> [Term] -> [Term] -> PreRules -> Trace -> [SExpr Pos] -> m (Gen, [Term], [Term], PreRules, Trace) loadTraceLoop gen newVars uniqs pr events [] = return (gen, (vars ++ (reverse newVars)), (reverse uniqs), pr, (reverse events)) loadTraceLoop gen newVars uniqs pr events ((L _ [S _ "recv", t]) : rest) = do t <- loadTerm sig vars True t loadTraceLoop gen newVars uniqs pr ((In $ Plain t) : events) rest loadTraceLoop gen newVars uniqs pr events ((L _ [S _ "send", t]) : rest) = do t <- loadTerm sig vars True t loadTraceLoop gen newVars uniqs pr ((Out $ Plain t) : events) rest loadTraceLoop gen newVars uniqs pr events ((L _ [S _ "recv", ch, t]) : rest) = do ch <- loadChan sig vars ch t <- loadTerm sig vars True t loadTraceLoop gen newVars uniqs pr ((In $ ChMsg ch t) : events) rest loadTraceLoop gen newVars uniqs pr events ((L _ [S _ "send", ch, t]) : rest) = do ch <- loadChan sig vars ch t <- loadTerm sig vars True t loadTraceLoop gen newVars uniqs pr ((Out $ ChMsg ch t) : events) rest loadTraceLoop gen newVars uniqs pr events ((L _ [S pos "load", ch, t]) : rest) = do ch <- loadLocn sig vars ch t <- loadTerm sig vars True t (gen, pt, pt_t) <- loadLocnTerm sig gen (S pos "pt") (S pos "pval") t loadTraceLoop gen (pt : newVars) uniqs pr ((In $ ChMsg ch pt_t) : events) rest loadTraceLoop gen newVars uniqs pr events ((L _ [S pos "stor", ch, t]) : rest) = do ch <- loadLocn sig vars ch t <- loadTerm sig vars True t (gen, pt, pt_t) <- loadLocnTerm sig gen (S pos "pt") (S pos "pval") t loadTraceLoop gen (pt : newVars) (pt : uniqs) pr ((Out $ ChMsg ch pt_t) : events) rest loadTraceLoop gen newVars uniqs pr events ((L _ [S pos "rely", form]) : rest) = case events of [] -> fail (shows pos $ "Rely precedes first event: " ++ (show form)) (In _) : _ -> loadTraceLoop gen newVars uniqs (preRulesAddRely pr ((L.length events), form)) events rest _ -> fail (shows pos $ "Rely must follow recv or load: " ++ (show form)) loadTraceLoop gen newVars uniqs pr events ((L _ [S pos "guar", form]) : rest) = case rest of [] -> fail (shows pos $ "Guarantee follows last event: " ++ (show form)) (L _ (S _ "stor" : _) : _) -> loadTraceLoop gen newVars uniqs (preRulesAddGuar pr (1+(L.length events), form)) events rest (L _ (S _ "send" : _) : _) -> loadTraceLoop gen newVars uniqs (preRulesAddGuar pr (1+(L.length events), form)) events rest _ -> fail (shows pos $ "Guarantee must precede send or stor: " ++ (show form)) loadTraceLoop gen newVars uniqs pr events ((L _ [S pos "cheq", src, tgt]) : rest) = case rest of [] -> fail (shows pos $ "cheq must precede some event: " ++ (show src) ++ ", " ++ (show tgt)) _ -> do src <- loadTerm sig vars False src tgt <- loadTerm sig vars False tgt loadTraceLoop gen newVars uniqs (preRulesAddCheq pr (1+(L.length events), pos, src, tgt)) events rest loadTraceLoop _ _ _ _ _ ((L pos [S _ dir, _, _]) : _) = fail (shows pos $ "Unrecognized direction " ++ dir) loadTraceLoop _ _ _ _ _ (x : _) = fail (shows (annotation x) "Malformed event") loadChan :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term loadChan sig vars x = do ch <- loadTerm sig vars False x case isChan ch || isLocn ch of True -> return ch False -> fail (shows (annotation x) "Expecting a channel or location") loadLocn :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term loadLocn sig vars x = do ch <- loadTerm sig vars False x case isLocn ch of True -> return ch False -> fail (shows (annotation x) "Expecting a location") loadBaseTerms :: MonadFail m => Sig -> [Term] -> [SExpr Pos] -> m [Term] loadBaseTerms _ _ [] = return [] loadBaseTerms sig vars (x : xs) = do t <- loadBaseTerm sig vars x ts <- loadBaseTerms sig vars xs return (adjoin t ts) loadBaseTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term loadBaseTerm sig vars x = do t <- loadTerm sig vars True x case isAtom t of True -> return t False -> fail (shows (annotation x) "Expecting an atom") loadPosBaseTerms :: MonadFail m => Sig -> [Term] -> [SExpr Pos] -> m [(Maybe Int, Term)] loadPosBaseTerms _ _ [] = return [] loadPosBaseTerms sig vars (x : xs) = do t <- loadPosBaseTerm sig vars x ts <- loadPosBaseTerms sig vars xs return (t:ts) loadPosBaseTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m (Maybe Int, Term) loadPosBaseTerm sig vars x'@(L _ [N _ opos, x]) | opos < 0 = fail (shows (annotation x') "Expecting a non-negative non-origination position") | otherwise = do t <- loadBaseTerm sig vars x return (Just opos, t) loadPosBaseTerm sig vars x = do t <- loadTerm sig vars True x case isAtom t of True -> return (Nothing, t) False -> fail (shows (annotation x) "Expecting an atom") loadAbsent :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m (Term, Term) loadAbsent sig vars (L _ [x, y]) = do x <- loadVarExprTerm sig vars x y <- loadExprTerm sig vars y return (x, y) loadAbsent _ _ x = fail (shows (annotation x) "Expecting a pair of terms") loadExprTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term loadExprTerm sig vars x = do t <- loadTerm sig vars False x case isExpr t of True -> return t False -> fail (shows (annotation x) "Expecting an exponent") loadVarExprTerm :: MonadFail m => Sig -> [Term] -> SExpr Pos -> m Term loadVarExprTerm sig vars x = do t <- loadTerm sig vars True x case isVarExpr t of True -> return t False -> fail (shows (annotation x) "Expecting an exponent variable") -- Find protocol and then load a preskeleton. findPreskel :: MonadFail m => Pos -> [Prot] -> [SExpr Pos] -> m Preskel findPreskel pos ps (S _ name : xs) = case L.find (\p -> name == pname p) ps of Nothing -> fail (shows pos $ "Protocol " ++ name ++ " unknown") Just p -> loadPreskel (psig p) pos p xs findPreskel pos _ _ = fail (shows pos "Malformed skeleton") loadPreskel :: MonadFail m => Sig -> Pos -> Prot -> [SExpr Pos] -> m Preskel loadPreskel sig pos p (L _ (S _ "vars" : vars) : xs) = do (gen, kvars) <- loadVars sig (pgen p) vars loadInsts sig pos p kvars gen [] xs loadPreskel _ pos _ _ = fail (shows pos "Malformed skeleton") loadInsts :: MonadFail m => Sig -> Pos -> Prot -> [Term] -> Gen -> [Instance] -> [SExpr Pos] -> m Preskel loadInsts sig top p kvars gen insts (L pos (S _ "defstrand" : x) : xs) = case x of S _ role : N _ height : env -> do (gen, i) <- loadInst sig pos p kvars gen role height env loadInsts sig top p kvars gen (i : insts) xs _ -> fail (shows pos "Malformed defstrand") loadInsts sig top p kvars gen insts (L pos (S _ "deflistener" : x) : xs) = case x of [term] -> do (gen, i) <- loadListener sig p kvars gen term loadInsts sig top p kvars gen (i : insts) xs _ -> fail (shows pos "Malformed deflistener") loadInsts sig top p kvars gen insts xs = do badKey ["defstrand", "deflistener"] xs _ <- alist [] xs -- Check syntax of xs (gen, gs) <- loadGoals sig top p gen goals loadRest sig top kvars p gen gs (reverse insts) order nr ar ur ug ab pr cn au fs pl leads genSts kcomment where order = assoc "precedes" xs nr = assoc "non-orig" xs ar = assoc "pen-non-orig" xs ur = assoc "uniq-orig" xs ug = assoc "uniq-gen" xs ab = assoc "absent" xs pr = assoc "precur" xs cn = assoc "conf" xs au = assoc "auth" xs fs = assoc "facts" xs pl = assoc "priority" xs leads = assoc "leads-to" xs goals = assoc "goals" xs genSts = assoc "gen-st" xs kcomment = loadComment "goals" goals ++ loadComment "comment" (assoc "comment" xs) loadComment :: String -> [SExpr Pos] -> [SExpr ()] loadComment _ [] = [] loadComment key comment = [L () (S () key : map strip comment)] loadInst :: MonadFail m => Sig -> Pos -> Prot -> [Term] -> Gen -> String -> Int -> [SExpr Pos] -> m (Gen, Instance) loadInst sig pos p kvars gen role height env = do r <- lookupRole pos p role case height < 1 || height > length (rtrace r) of True -> fail (shows pos "Bad height") False -> do let vars = rvars r (gen', env') <- foldM (loadMaplet sig kvars vars) (gen, emptyEnv) env return (mkInstance gen' r env' height) loadMaplet :: MonadFail m => Sig -> [Term] -> [Term] -> (Gen, Env) -> SExpr Pos -> m (Gen, Env) loadMaplet sig kvars vars env (L pos [domain, range]) = do t <- loadTerm sig vars False domain t' <- loadTerm sig kvars False range case match t t' env of env' : _ -> return env' [] -> fail (shows pos "Domain does not match range") loadMaplet _ _ _ _ x = fail (shows (annotation x) "Malformed maplet") loadListener :: MonadFail m => Sig -> Prot -> [Term] -> Gen -> SExpr Pos -> m (Gen, Instance) loadListener sig p kvars gen x = do t <- loadTerm sig kvars True x return $ mkListener p gen t loadRest :: MonadFail m => Sig -> Pos -> [Term] -> Prot -> Gen -> [Goal] -> [Instance] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr Pos] -> [SExpr ()] -> m Preskel loadRest sig pos vars p gen gs insts orderings nr ar ur ug ab pr cn au fs pl leads genSts comment = do case null insts of True -> fail (shows pos "No strands") False -> return () let heights = map height insts o <- loadOrderings heights orderings nr <- loadBaseTerms sig vars nr ar <- loadBaseTerms sig vars ar ur <- loadBaseTerms sig vars ur ug <- loadBaseTerms sig vars ug ab <- mapM (loadAbsent sig vars) ab pr <- mapM (loadNode heights) pr cn <- loadBaseTerms sig vars cn au <- loadBaseTerms sig vars au fs <- mapM (loadFact sig heights vars) fs lds <- loadOrderings heights leads genSts <- loadTerms sig vars genSts let (nr', ar', ur', ug', ab', cn', au') = foldl addInstOrigs (nr, ar, ur, ug, ab, cn, au) insts prios <- mapM (loadPriorities heights) pl let k = mkPreskel gen p gs insts o nr' ar' ur' ug' ab' pr genSts cn' au' fs prios comment ab'' = L.concatMap (\(x, y) -> [x, y]) ab' case termsWellFormed $ nr' ++ ar' ++ ur' ++ ug' ++ ab'' ++ kterms k of False -> fail (shows pos "Terms in skeleton not well formed") True -> return () case L.all isChan (cn' ++ au') of False -> fail (shows pos "Bad channel in role") True -> return () case verbosePreskelWellFormed k of Return () -> return $ applyLeadsTo k lds Fail msg -> fail $ shows pos $ showString "Skeleton not well formed: " msg loadOrderings :: MonadFail m => [Int] -> [SExpr Pos] -> m [Pair] loadOrderings heights x = foldM f [] x where f ns x = do np <- loadPair heights x return (adjoin np ns) loadPair :: MonadFail m => [Int] -> SExpr Pos -> m Pair loadPair heights (L pos [x0, x1]) = do n0 <- loadNode heights x0 n1 <- loadNode heights x1 case fst n0 == fst n1 of -- Same strand True -> fail (shows pos "Malformed pair -- nodes in same strand") False -> return (n0, n1) loadPair _ x = fail (shows (annotation x) "Malformed pair") loadNode :: MonadFail 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") loadFact :: MonadFail m => Sig -> [Int] -> [Term] -> SExpr Pos -> m Fact loadFact sig heights vars (L _ (S _ name : fs)) = do fs <- mapM (loadFterm sig heights vars) fs return $ Fact name fs loadFact _ _ _ x = fail (shows (annotation x) "Malformed fact") loadFterm :: MonadFail m => Sig -> [Int] -> [Term] -> SExpr Pos -> m FTerm loadFterm _ heights _ (N pos s) | 0 <= s && s < length heights = return $ FSid s | otherwise = fail (shows pos ("Bad strand in fact: " ++ show s)) loadFterm sig _ vars x = do t <- loadTerm sig vars False x return $ FTerm t loadPriorities :: MonadFail m => [Int] -> SExpr Pos -> m (Node, Int) loadPriorities heights (L _ [x, N _ p]) = do n <- loadNode heights x return (n, p) loadPriorities _ x = fail (shows (annotation x) "Malformed priority") addInstOrigs :: ([Term], [Term], [Term], [Term], [(Term, Term)], [Term], [Term]) -> Instance -> ([Term], [Term], [Term], [Term], [(Term, Term)], [Term], [Term]) addInstOrigs (nr, ar, ur, ug, ab, cn, au) i = (foldl (flip adjoin) nr $ inheritRnon i, foldl (flip adjoin) ar $ inheritRpnon i, foldl (flip adjoin) ur $ inheritRunique i, foldl (flip adjoin) ug $ inheritRuniqgen i, foldl (flip adjoin) ab $ inheritRabsent i, foldl (flip adjoin) cn $ inheritRconf i, foldl (flip adjoin) au $ inheritRauth i) -- Security goals -- Load a defgoal form findGoal :: MonadFail m => Pos -> [Prot] -> [SExpr Pos] -> m Preskel findGoal pos ps (S _ name : x : xs) = case L.find (\p -> name == pname p) ps of Nothing -> fail (shows pos $ "Protocol " ++ name ++ " unknown") Just p -> do let sig = psig p (g, goal, antec) <- loadSentence sig RoleSpec pos p (pgen p) x let (gs, xs') = findAlist xs (g, goals) <- loadGoals sig pos p g gs _ <- alist [] xs' -- Check syntax of xs let kcomment = loadComment "goals" (x : gs) ++ loadComment "comment" (assoc "comment" xs') -- Make and return the characteristic skeleton of a security goal characteristic pos p (goal : goals) g antec kcomment findGoal pos _ _ = fail (shows pos "Malformed goal") -- Separate argument into goals and any remaining elements of an -- association list. findAlist :: [SExpr Pos] -> ([SExpr Pos], [SExpr Pos]) findAlist [] = ([], []) findAlist (x@(L _ (S _ "forall" : _)) : xs) = (x : gs, xs') where (gs, xs') = findAlist xs findAlist xs = ([], xs) --- Load a sequence of security goals loadGoals :: MonadFail m => Sig -> Pos -> Prot -> Gen -> [SExpr Pos] -> m (Gen, [Goal]) loadGoals _ _ _ g [] = return (g, []) loadGoals sig pos prot g (x : xs) = do (g, goal, _) <- loadSentence sig RoleSpec pos prot g x (g, goals) <- loadGoals sig pos prot g xs return (g, goal : goals)