packages feed

cpsa-3.4.0: src/CPSA/Lib/Protocol.hs

-- Protocol data structures.

-- 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.Protocol (Event (..), evtTerms, evtMesgTerms, evtMap, evt,
    recvTerm, Trace, stripSync, tterms, originates,
    originationPos, gainedPos, genGainedPos, 
    Role, rname, rvars, rtrace, rdecls, rcomment, rsearch, generationPos,
    ridecls, mkRole, roleWellFormed,
    RoleDeclInst, RoleDeclInstList, RoleDeclaration, RoleDeclList,
    RoleDeclarations, rpriority, defaultPriority,
    Prot, mkProt, pname, alg, pgen, roles, listenerRole,
    varsAllAtoms, pcomment) where

import qualified Data.List as L
import qualified Data.Set as S
import qualified Data.Maybe as M
import CPSA.Lib.Utilities
import CPSA.Lib.SExpr
import CPSA.Lib.Algebra
import CPSA.Lib.AlgebraLibrary
import CPSA.Lib.Declaration
import CPSA.Lib.State

{--
import CPSA.Lib.Debug
--}

-- Message events and traces

data Event t
    = In !t                      -- Inbound message
    | Out !t                     -- Outbound message
    | Sync (Tran t)              -- State synchronization
    deriving (Show, Eq, Ord)

-- Dispatch to function based on direction.
evt :: (t -> a) -> (t -> a) -> (Tran t -> a) -> Event t -> a
evt inDir outDir syncDir evt =
    case evt of
      In t -> inDir t
      Out t -> outDir t
      Sync t -> syncDir t

-- Extract the terms in an event.
evtTerms :: Event t -> [t]
evtTerms (In t) = [t]
evtTerms (Out t) = [t]
evtTerms (Sync t) = tranTerms t

-- Extract the message-passed terms in an event.
evtMesgTerms :: Event t -> [t]
evtMesgTerms (Sync _) = []
evtMesgTerms e = evtTerms e

-- Map the term in an event.
evtMap :: (t -> t) -> Event t -> Event t
evtMap f (In t) = In (f t)
evtMap f (Out t) = Out (f t)
evtMap f (Sync t) = Sync (mapTran f t)

-- Extract the reception term
recvTerm :: Event t -> Maybe t
recvTerm (In t) = Just t
recvTerm _ = Nothing

{-
-- Extract the transmission term
sendTerm :: Event t -> Maybe t
sendTerm (Out t) = Just t
sendTerm _ = Nothing
-}

-- Extract the term in an inbound event.
inbnd :: Event t -> Maybe t
inbnd (In t) = Just t
inbnd (Sync (Tran (Just t,_,_))) = Just t
inbnd _ = Nothing

-- Extract the term in an outbound event.
outbnd :: Event t -> Maybe t
outbnd (Out t) = Just t
outbnd (Sync (Tran (_,Just t,_))) = Just t
outbnd _ = Nothing

{-
-- Extract the term in an outbound event.
sync :: Event t -> Maybe (Tran t)
sync (Sync t) = Just t
sync _ = Nothing
-}

-- A trace is a list of events.  The terms in the trace are
-- stored in causal order.
type Trace t = [Event t]

stripSync :: Trace t -> Trace t
stripSync [] = []
stripSync (Sync _:c) = stripSync c
stripSync (e:c) = e:stripSync c

-- The set of terms in a trace.
tterms :: Eq t => Trace t -> [t]
tterms c =
  L.nub [t | evt <- c, t <- evtTerms evt]

tmesgterms :: Eq t => Trace t -> [t]
tmesgterms c =
  L.nub [t | evt <- c, t <- evtMesgTerms evt]

-- Is the term carried by an event, and is the first one outgoing?
originates :: Algebra t p g s e c => t -> Trace t -> Bool
originates t c = M.isJust (originationPos t c)

-- Loop through the events in a trace, looking for an inbound
-- message satisfying (intest1) or (intest2), in which case,
-- return Nothing or Just (height) respectively, or for an
-- outbound message satisfying (outtest1) or (outtest2), in
-- which case return Nothing or Just (height) respecitvely,
-- with Nothing as a default.
--
-- If both intest1 and intest2 are true, return Just (height).
-- If both outtest1 and outtest2 are true, return Just (height).
lookupPos :: Algebra t p g s e c => t -> (t -> t -> Bool) ->
             (t -> t -> Bool) -> (t -> t -> Bool) -> (t -> t -> Bool) ->
             Trace t -> Maybe Int
lookupPos t intest1 intest2 outtest1 outtest2 c =
    loop 0 c
    where
    loop _ [] = Nothing         -- Default to Nothing
    loop pos (evt : c) =
         if bail then inresult else result
         where
           bail = case (inbnd evt) of
             Nothing -> False
             Just t' -> (intest1 t t') || (intest2 t t')
           inresult = case (inbnd evt) of
             Nothing -> error ("protocol:lookupPos -- no result defined.")
             Just t' -> if (intest2 t t') then Just pos else Nothing
           result = case (outbnd evt) of
             Nothing -> loop (pos + 1) c
             Just t' -> if (outtest2 t t') then Just pos else
                       (if (outtest1 t t') then Nothing else loop (pos + 1) c)

-- At what position does a term originate in a trace?
originationPos :: Algebra t p g s e c => t ->
                  Trace t -> Maybe Int
originationPos t c =
    -- Looking for where t is first carried, if it is in an outbound message.
    lookupPos t carriedBy ftest ftest carriedBy c

-- At what position does a term generate in a trace?
generationPos :: Algebra t p g s e c => t ->
                  Trace t -> Maybe Int
generationPos t c =
    -- Looking for where t first occurs, if it is in an outbound message.
    lookupPos t constituent ftest ftest constituent c

-- At what position is a term acquired in a trace?
acquiredPos :: Algebra t p g s e c => t -> Trace t -> Maybe Int
acquiredPos t c =
    -- Looking for where t first occurs, if it is carried there and that is an
    -- inbound message
    lookupPos t occursIn carriedBy occursIn ftest c

-- At what position is a term obtained in a trace?
obtainedPos :: Algebra t p g s e c => t -> Trace t -> Maybe Int
obtainedPos t c =
    -- Looking for where t first occurs, if it occurs there and that is an
    -- inbound message
    lookupPos t occursIn occursIn occursIn ftest c

-- At what position is a term gained in a trace?
gainedPos :: Algebra t p g s e c => t ->
             Trace t -> Maybe Int
gainedPos t c =
    -- Looking for where t is first carried, if it is in an inbound message.
    lookupPos t ftest carriedBy carriedBy ftest c

-- At what position is a term gained in a trace with regards to constituent?
genGainedPos :: Algebra t p g s e c => t ->
             Trace t -> Maybe Int
genGainedPos t c =
    -- Looking for where t first occurs, if it is in an inbound message.
    lookupPos t ftest constituent constituent ftest c

ftest :: a -> b -> Bool
ftest = const (const False)

-- At what position do all of the variables in a term occur in a trace?
usedPos :: Algebra t p g s e c => t -> Trace t -> Maybe Int
usedPos t c =
    loop 0 (varsInTerms [t]) c
    where
      loop _ _ [] = Nothing
      loop pos vars (e : c) =
          let vars' = [ x | x <- vars, notElem x (varsInTerms (evtTerms e)) ] in
          case vars' of
            [] -> Just pos
            _ -> loop (pos + 1) vars' c

-- Roles store internal declarations only in ridecls.
-- Roles store a raw external declarations format.
data Role t = Role
    { rname :: !String,
      rvars :: ![t],            -- Set of role variables
                                -- Events in causal order
      rtrace :: ![Event t],
      -- Set of non-originating atoms, possibly with a trace length
      rdecls :: RoleDeclarations t,
      rpriority :: ![Int],      -- Priorities for each event
      rcomment :: [SExpr ()],   -- Comments from the input
      rsearch :: Bool, -- True when suggesting reverse test node search
      ridecls :: [(RoleDeclarations t, Int)]}
    deriving Show

defaultPriority :: Int
defaultPriority = 5

-- The empty role name is used with listener strands.  All roles in a
-- protocol must have a name with more than one character.

-- The lists vars, non, pnon, and unique are sets and should never
-- contain duplicate terms.

-- Create a role
mkRole :: Algebra t p g s e c => String -> [t] -> Trace t ->
          RoleDeclList t -> [(Int, Int)] -> [SExpr ()] -> Bool -> Role t
mkRole name vars trace dlist priority comment rev =
    Role { rname = name,
           rvars = L.nub vars,  -- Every variable here must
           rtrace = trace,      --  occur in the trace.
           rdecls = mkDecls dlist',
           rcomment = comment,
           rpriority = addDefaultPrio priority,
           ridecls = concatMap mkRidecls $ nats(length(trace)),
           rsearch = rev
         }
    where
      dlist' = expandDecls vars trace dlist
      rothersintro = map addOtherOrig $ dlist'
      restrict i dlist = filter (\x -> not (null (snd x))) (select i dlist)
      select i dlist = map (\ (n, dis) -> (n, map fst
                                              (filter (\ (_,j)->i==j) dis))) dlist
      mkRidecls i =
        let os = restrict i rothersintro in
        [(mkDecls os, i)]
      addOtherOrig (n,ds) = (n, map addDeclOrig ds)
      addDeclOrig dinst
            | null ls && null ts = (dinst,0)
            | any (==Nothing) tps =
              error "Protocol.mkRole: Declaration variables not in trace"
            | any (\i -> (i < 0 || i >= length trace)) ls =
              error "Protocol.mkRole: Declaration height not valid"
            | null ts = (dinst,maximum ls)
            | null ls = (dinst,maximum (map M.fromJust tps))
            | otherwise = (dinst,max (maximum ls) (maximum (map M.fromJust tps)))
            where
               ts = dterms dinst
               ls = dlocs dinst
               tps = map (\t -> usedPos t trace) ts
      addDefaultPrio priority =
          map f (nats $ length trace)
          where
            f n =
              case lookup n priority of
                Nothing -> defaultPriority
                Just p -> p

-- expandDecls: transform declarations from file format to internal format.
-- This means to (1) add nodes to uniq-orig and uniq-gen declarations (throwing
-- an error if there is no appropriate node), (2) adding ind-zero declarations for
-- generating exponents not declared uniq-gen, and (3) adding ind-zero-in
-- declarations for exponents before a generation point of a uniq-gen exponent.
expandDecls :: Algebra t p g s e c => [t] -> Trace t ->
               RoleDeclList t -> RoleDeclList t
expandDecls _ trace dlist =
  (map addUniqs dlist) ++ indzeros
  where
    -- add locations to uniq-orig and uniq-gen declarations
    addUniqs ("uniq-orig", ds) = ("uniq-orig", map addUniqOrig ds)
    addUniqs ("uniq-gen", ds) = ("uniq-gen", map addUniqGen ds)
    -- leave other declarations alone
    addUniqs (n, ds) = (n, ds)
    indzeros = indz_ins
    -- ind-zero-in declarations: for numeric subterms occurring earlier than
    -- the generation point of uniq-gen numeric variables.
    indz_ins = case concatMap indz_ininsts (filter isNum (dkuniqgen decls)) of
      [] -> []
      insts -> [("absent", insts)]
    indz_ininsts v =
      case generationPos (v) trace of
        Nothing -> error "Protocol.mkRole: Atom does not generate"
        Just p -> indz_ininsts_var v p
    -- ind-zero instances for a specific variable v that generates at height p
    indz_ininsts_var v p =
      map (\t -> (declInst [v,t] [])) (numsUpTo p)
    -- returns a list of numeric subterms of all messages prior to height p.
    numsUpTo p =
      concatMap S.toList $ map subNums $ concatMap evtTerms $ take p trace
    -- avoid = avoidTerms decls
    decls = mkDecls dlist
    addUniqOrig dinst
      | null $ dterms dinst =
        error "Protocol.mkRole: malformed uniq-orig declaration"
      | otherwise =
        case originationPos (t) trace of
          Just p -> (declInst [t] [p])
          Nothing -> error "Protocol.mkRole: Atom does not originate"
      where
        t = head $ dterms dinst
    addUniqGen dinst
      | null $ dterms dinst =
        error "Protocol.mkRole: malformed uniq-gen declaration"
      | otherwise =
        case generationPos (t) trace of
          Just p -> (declInst [t] [p])
          Nothing -> error "Protocol.mkRole: Atom does not generate"
      where
        t = head $ dterms dinst

-- 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 :: (Monad m, Algebra t p g s e c) => Role t -> m ()
roleWellFormed role =
    do
      failwith "[ASSERT FAILED] invalid declaration"
                   $ all declarationValid (map fst $ declarationTags $ rdecls role)
      mapM_ nonCheck $ dknon $ rdecls role
      mapM_ uniqueCheck $ dkunique $ rdecls role
      let chk = declCheck $ rdecls role
      failwith (snd chk) (fst chk)
      mapM_ acqVarCheck $ rvars role
      mapM_ obtVarCheck $ rvars role
      failwith "role trace is a prefix of a listener"
                   $ notListenerPrefix $ rtrace role
    where
      declarationValid tag = all declInstValid $ tagDecls tag $ rdecls role
      declInstValid dinst =
          (all lenCheck $ dlocs dinst) && (varSubset (dterms dinst) terms)
      terms = tterms (rtrace role)
      nonCheck t =
          failwith (showString "non-orig " $ showst t " carried")
                       $ all (not . carriedBy t) (tmesgterms (rtrace role))
      lenCheck n = (n >= 0 && n < (length $ rtrace role))

      uniqueCheck t =
          failwith (showString "[ASSERT FAILED] uniq-orig " $ showst  t " doesn't originate")
                       $ originates t (rtrace role)
      acqVarCheck v =
          failwith (showString "variable " $ showst v " not acquired")
                       $ not (isAcquiredVar v) ||
                         M.isJust (acquiredPos v (rtrace role))
      obtVarCheck v =
          failwith (showString "variable " $ showst v " not obtained")
                       $ not (isObtainedVar v) ||
                         M.isJust (obtainedPos v (rtrace role))

showst :: Algebra t p g s e c => t -> ShowS
showst t =
    shows $ displayTerm (addToContext emptyContext [t]) t

-- Ensure a trace is not a prefix of a listener
notListenerPrefix :: Algebra t p g s e c => Trace t -> Bool
notListenerPrefix (In t : Out t' : _) | t == t' = False
notListenerPrefix _ = True

-- Protocols

data Prot t g
    = Prot { pname :: !String,  -- Name of the protocol
             alg :: !String,    -- Name of the algebra
             pgen :: !g,        -- Initial variable generator
             roles :: ![Role t], -- Non-listener roles of a protocol
             listenerRole :: Role t,
             varsAllAtoms :: !Bool,   -- Are all role variables atoms?
             pcomment :: [SExpr ()] }  -- Comments from the input
    deriving Show

-- Callers should ensure every role has a distinct name.
mkProt :: Algebra t p g s e c => String -> String ->
          g -> [Role t] -> Role t -> [SExpr ()] -> Prot t g
mkProt name alg gen roles lrole comment =
    Prot { pname = name, alg = alg, pgen = gen, roles = roles,
           listenerRole = lrole, pcomment = comment,
           varsAllAtoms = all roleVarsAllAtoms roles }
    where
      roleVarsAllAtoms role = all isAtom (rvars role)

type RoleDeclInst t = DeclInst t Int
type RoleDeclInstList t = DeclInstList t Int
type RoleDeclaration t = Declaration t Int
type RoleDeclList t = DeclList t Int
type RoleDeclarations t = Declarations t Int