{-# LANGUAGE DeriveDataTypeable #-} module Scyther.Equalities ( -- * Single Equalities TIDEq , TIDRoleEq , RoleEq , ArbMsgEq , AVarEq , MVarEq , MsgEq , AnyEq(..) , Inequality(..) , arbmEqToMsgEq , mvarEqToMsgEq -- * Compound Equalities , Equalities -- ** Construction , empty , solve , trimTIDEqs , trimArbMsgEqs -- ** Destruction , getTIDEqs , getTIDRoleEqs , getArbMsgEqs , getAVarEqs , getMVarEqs , getPostEqs , toAnyEqs , anyEqTIDs , anyEqAMIDs , inequalityTIDs , inequalityAMIDs -- ** Substitution , substTID , substLocalId , substAVar , substMVar , substAMID , substMsg , substAnyEq , substInequality -- ** Additional Queries , threadRole , maxMappedTID , maxMappedArbMsgId , reflexive , falseIneq , null -- * Mapping Logical Variables , Mapping(..) , emptyMapping , mkMapping , addTIDMapping , addArbMsgIdMapping , addTIDRoleMapping , deleteTIDMapping , deleteArbMsgIdMapping -- * Pretty Printing , sptAnyEq , sptInequality ) where import Prelude hiding (null) import qualified Data.List as L import qualified Data.Map as M import qualified Data.UnionFind as U import Data.Data import Control.Arrow ( (***) ) import Control.Monad import Text.Isar import Scyther.Protocol import Scyther.Message ------------------------------------------------------------------------------ -- Equalities ------------------------------------------------------------------------------ -- | Equalities over thread identifers. -- -- Logically these are equalities between logical thread identifier variables. type TIDEq = (TID, TID) type TIDEqs = M.Map TID TID -- | A thread to role assignment. type TIDRoleEq = (TID, Role) type TIDRoleEqs = M.Map TID Role -- | The role equalities serve a double function: type RoleEq = (Role, Role) -- | An equality on an arbitrar-message id (a logical message variable). type ArbMsgEq = (ArbMsgId, Message) -- | Like role equalities, the agent equalities specify both quantification and -- equalities. type ArbMsgEqs = M.Map ArbMsgId Message -- | Equalities between different agent variables. -- -- We do not have to reason about equalities between an agent variable and some -- other message because our semantics guarantees that agent variables are only -- instantiated with agent names. Hence, they can only be equal to other agent -- variables or message variables. In the latter case, we store the equality -- the other way round; assigning the agent variable to the message variable. type AVarEq = (AVar, AVar) type AVarEqs = M.Map AVar AVar -- | Equalities between message variables and arbitrary messages. type MVarEq = (MVar, Message) type MVarEqs = M.Map MVar Message -- | Equalities between messages. type MsgEq = (Message, Message) type MsgEqs = U.UnionFind Message -- | Some representable equality. data AnyEq = TIDEq !TIDEq | TIDRoleEq !TIDRoleEq | RoleEq !RoleEq | ArbMsgEq !ArbMsgEq | AVarEq !AVarEq | MVarEq !MVarEq | MsgEq !MsgEq deriving( Eq, Ord, Show, Data, Typeable ) -- | An inequality. Bound logical variables are universally quantified. newtype Inequality = Inequality { getInequality :: AnyEq } deriving( Eq, Ord, Show, Data, Typeable ) -- | A conjunction of equality facts. -- -- Invariants for a value @eqs = Equalities tideqs roleeqs avareqs mvareqs arbmeqs@: -- -- 1. Domain and range normalized with respect to equalities. Note that this implies -- substitution must always consider TID substitution first. -- -- forall tid : ran(tideqs). substTID eqs tid = tid -- forall tid : dom(roleeqs). substTID eqs tid = tid -- forall (lid, lid') : avareqs. substLocalId eqs lid = lid -- & substAVar eqs lid' = lid' -- forall (lid, m) : mvareqs. substLocalId eqs lid = lid -- & substMsg eqs m = m -- -- TODO: Complete properties for Agent ID's -- -- forall aid : dom(arbmeqs). substAMID eqs aid = MArbMsg aid -- -- 2. Origin always greater than image for symmetric eqs. -- -- forall (tid, tid') : tideqs. tid > tid' -- forall (lid, lid') : avareqs. lid > lid' -- -- 3. Range of message variable equalities normalized with respect to key inversion. -- -- forall m : ran(mvareqs). normMsg m = m -- -- 4. All thread identifiers are in the domain of roleeqs. -- -- 5. All agent identifiers are in the domain of arbmeqs. -- -- 6. No cycles. -- data Equalities = Equalities { tidEqs :: TIDEqs -- ^ Thread identifier equalities. , roleEqs :: TIDRoleEqs -- ^ Thread to role assignments. , avarEqs :: AVarEqs -- ^ Equalities between agent variables. , mvarEqs :: MVarEqs -- ^ Equalities between message variables and arbitrary messages. , arbmEqs :: ArbMsgEqs -- ^ Equalities between logical message variables and other messages. , postEqs :: MsgEqs -- ^ Postponed equalities that stem from equalities involving -- bi-directional keys and cannot be solved without -- splitting in their most general form @KShr a b = KShr c d@. We -- try to exploit these postponed equalities after -- every regular unification. } deriving( Eq, Ord, Show, Data, Typeable {-! NFData !-} ) -- | Empty equality premises. empty :: Equalities empty = Equalities M.empty M.empty M.empty M.empty M.empty U.empty -- | True if no equalities are present. null :: Equalities -> Bool null = (==) empty -- Substitution --------------- -- NOTE: Here we exploit that range of equalities does not contain any -- identifier from the domain. -- | Substitute a thread identifier according to thread identifier equalities. substTID :: Equalities -> TID -> TID substTID eqs tid = M.findWithDefault tid tid (tidEqs eqs) -- | Substitute a local identifier according to thread identifier equalities. substLocalId :: Equalities -> LocalId -> LocalId substLocalId eqs (LocalId (i, tid)) = (LocalId (i, substTID eqs tid)) -- | Substitute a local identifier belonging to an agent variable. substAVar :: Equalities -> AVar -> AVar substAVar eqs av = M.findWithDefault av' av' (avarEqs eqs) where av' = mapAVar (substLocalId eqs) av -- | Substitute a local identifier belonging to a message variable. substMVar :: Equalities -> MVar -> Message substMVar eqs mv = M.findWithDefault (MMVar mv') mv' (mvarEqs eqs) where mv' = mapMVar (substLocalId eqs) mv -- | Substitute an agent id representing an arbitrary agent name. substAMID :: Equalities -> ArbMsgId -> Message substAMID eqs aid = M.findWithDefault (MArbMsg aid) aid (arbmEqs eqs) -- | Substitute message constituents according to equalities. -- -- POST: Message normalized w.r.t. 'normMsg'. substMsg :: Equalities -> Message -> Message substMsg eqs = normMsg . go where go m@(MConst _) = m go (MFresh fr) = MFresh (mapFresh (substLocalId eqs) fr) go (MAVar av) = MAVar (substAVar eqs av) go (MMVar mv) = substMVar eqs mv go (MArbMsg aid) = substAMID eqs aid go (MHash m) = MHash (go m) go (MTup m1 m2) = MTup (go m1) (go m2) go (MEnc m1 m2) = MEnc (go m1) (go m2) go (MSymK m1 m2) = MSymK (go m1) (go m2) go (MShrK m1 m2) = U.findWithDefault m' m' $ postEqs eqs where m' = MShrK (go m1) (go m2) go (MAsymPK m) = MAsymPK (go m) go (MAsymSK m) = MAsymSK (go m) go (MInvKey m) = MInvKey (go m) -- | Substitute a thread id equalitiy. substTIDEq :: Equalities -> TIDEq -> TIDEq substTIDEq eqs = substTID eqs *** substTID eqs -- | Substitute a thread id to role equality. substTIDRoleEq :: Equalities -> TIDRoleEq -> AnyEq substTIDRoleEq eqs (tid, role) = case threadRole tid' eqs of Just role' -> RoleEq (role, role') Nothing -> TIDRoleEq (tid', role) where tid' = substTID eqs tid -- | Substitute an agent equality. substArbMsgEq :: Equalities -> ArbMsgEq -> MsgEq substArbMsgEq eqs = substAMID eqs *** substMsg eqs -- | Substitute an agent variable equality. substAVarEq :: Equalities -> AVarEq -> AVarEq substAVarEq eqs = substAVar eqs *** substAVar eqs -- | Substitute a message variable equality. substMVarEq :: Equalities -> MVarEq -> MsgEq substMVarEq eqs = substMVar eqs *** substMsg eqs -- | Substitute a message equality. substMsgEq :: Equalities -> MsgEq -> MsgEq substMsgEq eqs = substMsg eqs *** substMsg eqs -- | Substitute both sides of a representable equality. substAnyEq :: Equalities -> AnyEq -> AnyEq substAnyEq eqs eq0 = case eq0 of TIDEq eq -> TIDEq $ substTIDEq eqs eq TIDRoleEq eq -> substTIDRoleEq eqs eq RoleEq _ -> eq0 ArbMsgEq eq -> MsgEq $ substArbMsgEq eqs eq AVarEq eq -> AVarEq $ substAVarEq eqs eq MVarEq eq -> MsgEq $ substMVarEq eqs eq MsgEq eq -> MsgEq $ substMsgEq eqs eq -- | Substitute both sides of an inequality. substInequality :: Equalities -> Inequality -> Inequality substInequality eqs = Inequality . substAnyEq eqs . getInequality -- Checking for reflexivity --------------------------- -- | Convert an agent equality to a message equality. arbmEqToMsgEq :: ArbMsgEq -> MsgEq arbmEqToMsgEq (aid, rhs) = (MArbMsg aid, rhs) -- | Convert a message variable equallity to a message equality. mvarEqToMsgEq :: MVarEq -> MsgEq mvarEqToMsgEq (v, m) = (MMVar v, m) -- | Check if an equality is reflexive. reflexive :: AnyEq -> Bool reflexive eq0 = case eq0 of TIDEq eq -> uncurry (==) eq TIDRoleEq _ -> False RoleEq eq -> uncurry (==) eq ArbMsgEq eq -> reflexive . MsgEq $ arbmEqToMsgEq eq AVarEq eq -> uncurry (==) eq MVarEq eq -> reflexive . MsgEq $ mvarEqToMsgEq eq MsgEq eq -> uncurry (==) eq -- Deconstruction ----------------- -- | The list of thread identifier equalities. getTIDEqs :: Equalities -> [TIDEq] getTIDEqs = M.toList . tidEqs -- | The list of role equalities. getTIDRoleEqs :: Equalities -> [TIDRoleEq] getTIDRoleEqs = M.toList . roleEqs -- | The list of agent variable equalities. getArbMsgEqs :: Equalities -> [ArbMsgEq] getArbMsgEqs = M.toList . arbmEqs -- | The list of agent variable equalities. getAVarEqs :: Equalities -> [AVarEq] getAVarEqs = M.toList . avarEqs -- | The list of message variable equalities. getMVarEqs :: Equalities -> [MVarEq] getMVarEqs = M.toList . mvarEqs -- | The list of postponed message equalities. getPostEqs :: Equalities -> [MsgEq] getPostEqs = U.toList . postEqs -- | Convert a set of equalities ot the tuple with lists for each individual -- equality type. toLists :: Equalities -> ([TIDEq], [TIDRoleEq], [ArbMsgEq], [AVarEq], [MVarEq], [MsgEq]) toLists eqs = (getTIDEqs eqs, getTIDRoleEqs eqs, getArbMsgEqs eqs , getAVarEqs eqs, getMVarEqs eqs, getPostEqs eqs) -- | Convert a set of equalities to a list of equalities. -- -- POST: Order of equalities equal to order in result of 'toLists'. toAnyEqs :: Equalities -> [AnyEq] toAnyEqs eqs = map TIDEq a ++ map TIDRoleEq b ++ map ArbMsgEq c ++ map AVarEq d ++ map MVarEq e ++ map MsgEq f where (a, b, c, d, e, f) = toLists eqs -- | The threads occurring in an equality. anyEqTIDs :: AnyEq -> [TID] anyEqTIDs eq = case eq of TIDEq (tid, _) -> return tid TIDRoleEq (tid, _) -> return tid RoleEq (_, _) -> mzero ArbMsgEq (_, m) -> msgTIDs m AVarEq (a1, a2) -> return (avarTID a1) `mplus` return (avarTID a2) MVarEq (v, m) -> return (mvarTID v) `mplus` msgTIDs m MsgEq (m1, m2) -> msgTIDs m1 `mplus` msgTIDs m2 -- | The logical message variables occuring in an equality. anyEqAMIDs :: AnyEq -> [ArbMsgId] anyEqAMIDs eq = case eq of TIDEq _ -> mzero TIDRoleEq _ -> mzero RoleEq _ -> mzero ArbMsgEq (a, m) -> a : msgAMIDs m AVarEq _ -> mzero MVarEq (_, m) -> msgAMIDs m MsgEq (m1, m2) -> msgAMIDs m1 `mplus` msgAMIDs m2 -- | The threads occurring in an inequality. inequalityTIDs :: Inequality -> [TID] inequalityTIDs = anyEqTIDs . getInequality -- | The logical message variables occuring in an inequality. inequalityAMIDs :: Inequality -> [ArbMsgId] inequalityAMIDs = anyEqAMIDs . getInequality -- Unification -------------- -- | Substitute and normalize the postponed equalities with respect to the -- other equalities. normPostEqs :: Equalities -> Equalities normPostEqs eqs0 = eqs { postEqs = U.map (substMsg eqs) (postEqs eqs0) } where eqs = eqs0 { postEqs = U.empty } -- | Solve a list of unification equations. -- -- The unification is performed modulo key inversion and thread identifier -- equalities. Additional thread identifier equalities may result from -- equalities over fresh messages. Bidirectional keys are handled by delaying -- their solution until only one solution is possible. solve :: Monad m => [AnyEq] -> Equalities -> m Equalities solve ueqs eqs = -- trace ("SOLVE: " ++ render (fsep $ punctuate comma $ map sptAnyEq ueqs)) $ fst `liftM` solveRepeated ueqs eqs False -- | Repeatedly solve unification equations until the solution doesn't change -- anymore. Postponed equations are tried to be solved again after each full -- iteration. -- -- A return value @(neweqs, improvedsolution)@ is to be interpreted such that -- @improvedsolution@ denotes that something apart from the postponed -- equalities has changed. solveRepeated :: Monad m => [AnyEq] -> Equalities -> Bool -> m (Equalities, Bool) solveRepeated [] eqs False = return (eqs, False) solveRepeated [] eqs True = solveRepeated (map MsgEq $ getPostEqs eqs) (eqs { postEqs = U.empty }) False solveRepeated (ueq:ueqs) eqs improved = do (ueqs', eqs', improved') <- solve1 ueq eqs solveRepeated (ueqs ++ ueqs') (normPostEqs eqs') (improved || improved') -- | Solve a single unification equation. solve1 :: Monad m => AnyEq -> Equalities -> m ([AnyEq], Equalities, Bool) solve1 ueq eqs@(Equalities tideqs roleeqs aveqs mveqs arbmeqs posteqs) = -- trace ("solve1: " ++ show (sptAnyEq ueq)) $ case ueq of TIDEq (tid1, tid2) -> let tid1' = substTID eqs tid1 tid2' = substTID eqs tid2 elimTID x y = return ( mkAnyEqs TIDRoleEq roleeqs ++ mkAnyEqs ArbMsgEq arbmeqs ++ mkAnyEqs AVarEq aveqs ++ mkAnyEqs MVarEq mveqs ++ map MsgEq (U.toList posteqs) , empty { tidEqs = M.insert x y tideqs } , True ) where mkAnyEqs :: ((k, v) -> AnyEq) -> M.Map k v -> [AnyEq] mkAnyEqs constr = map constr . M.toList in elimVarEqVar elimTID (tid1', tid1') (tid2', tid2') TIDRoleEq (tid, role) -> let tid' = substTID eqs tid in case M.lookup tid' roleeqs of Just role' | role' /= role -> different "role" role role' _ -> updateSolution (eqs { roleEqs = M.insert tid' role roleeqs }) RoleEq (role1, role2) | role1 == role2 -> skipEq | otherwise -> different "role" role1 role2 AVarEq (av1, av2) -> let av1' = substAVar eqs av1 av2' = substAVar eqs av2 elimAVar x y = updateSolution (eqs { mvarEqs = M.map (substMsg elimEqs) mveqs , arbmEqs = M.map (substMsg elimEqs) arbmeqs , avarEqs = M.insert x y $ M.map (substAVar elimEqs) aveqs }) where elimEqs = empty { avarEqs = M.singleton x y } in elimVarEqVar elimAVar (av1', av1') (av2', av2') ArbMsgEq (lhs, rhs) -> let elimArbMsgId x y | x `elem` msgAMIDs y = case y of MInvKey _ -> postpone (MArbMsg x) y -- Here, we have to postpone the equality as 'x = inv(x)' -- has a solution. -- -- This is a hacky solution. The real solution is to -- implement an occurs check strong enough to deal with the -- 'inv(inv(x)) = x' cancellation rule. -- -- FIXME: Implement proper equational unification, as done -- in the tamarin-prover. _ -> noUnifier $ "occurs check failed for '" ++ show x ++ "' in '" ++ show y ++ "'" | otherwise = updateSolution (eqs { mvarEqs = M.map (substMsg elimEqs) mveqs , arbmEqs = M.insert x y $ M.map (substMsg elimEqs) arbmeqs }) where elimEqs = empty { arbmEqs = M.singleton x y } in case (substAMID eqs lhs, substMsg eqs rhs) of (lhs'@(MArbMsg aid1), rhs'@(MArbMsg aid2)) -> elimVarEqVar elimArbMsgId (aid1, lhs') (aid2, rhs') (lhs' , (MArbMsg aid2)) -> elimArbMsgId aid2 lhs' ((MArbMsg aid1), rhs' ) -> elimArbMsgId aid1 rhs' (lhs' , rhs' ) -> newEqs [MsgEq (lhs', rhs')] MVarEq (lhs, rhs) -> let elimMVar x y | x `elem` msgFMV y = case y of MInvKey _ -> postpone (MMVar x) y -- Here, we have to postpone the equality as 'x = inv(x)' -- has a solution. -- -- This is a hacky solution. The real solution is to -- implement an occurs check strong enough to deal with the -- 'inv(inv(x)) = x' cancellation rule. _ -> noUnifier $ "occurs check failed for '" ++ show x ++ "' in '" ++ show y ++ "'" | otherwise = updateSolution (eqs { mvarEqs = M.insert x y $ M.map (substMsg elimEqs) mveqs }) where elimEqs = empty { mvarEqs = M.singleton x y } in case (substMVar eqs lhs, substMsg eqs rhs) of (lhs'@(MMVar mv1), rhs'@(MMVar mv2)) -> elimVarEqVar elimMVar (mv1, lhs') (mv2, rhs') (lhs' , (MMVar mv2)) -> elimMVar mv2 lhs' ( (MMVar mv1), rhs' ) -> elimMVar mv1 rhs' (lhs' , rhs' ) -> newEqs [MsgEq (lhs', rhs')] MsgEq eq -> case (substMsg eqs *** substMsg eqs) eq of -- The order of pattern matches ensures that message variables are always -- substituted by arbitrary-message ids. (MMVar mv1, rhs) -> newEqs [MVarEq (mv1, rhs)] (lhs, MMVar mv2) -> newEqs [MVarEq (mv2, lhs)] (MArbMsg aid1, rhs) -> newEqs [ArbMsgEq (aid1, rhs)] (lhs, MArbMsg aid2) -> newEqs [ArbMsgEq (aid2, lhs)] (MInvKey x, MInvKey y ) -> newEqs [MsgEq (x, y)] (MInvKey x, MAsymPK m1) -> newEqs [MsgEq (x, MAsymSK m1)] (MAsymPK m1, MInvKey x ) -> newEqs [MsgEq (x, MAsymSK m1)] (MInvKey x, MAsymSK m1) -> newEqs [MsgEq (x, MAsymPK m1)] (MAsymSK m1, MInvKey x ) -> newEqs [MsgEq (x, MAsymPK m1)] -- We postpone equalities between messages and inversions if both of -- them may still be an asymmetric key. Otherwise, the inversion can be -- eliminated as it is applied to a symmetric key. (m1, m2@(MInvKey x)) | mayBeAsymKeys [m1, x] -> postpone m1 m2 | otherwise -> newEqs [MsgEq (x, m1)] (m1@(MInvKey x), m2) | mayBeAsymKeys [x, m2] -> postpone m1 m2 | otherwise -> newEqs [MsgEq (x, m2)] (MAVar av1, MAVar av2) -> newEqs [AVarEq (av1, av2)] (MFresh (Fresh fr1), MFresh (Fresh fr2)) | lidId fr1 == lidId fr2 -> newEqs [TIDEq (lidTID fr1, lidTID fr2)] | otherwise -> different "nonce" fr1 fr2 (MHash m1, MHash m2 ) -> newEqs [MsgEq (m1, m2)] (MTup m11 m12, MTup m21 m22 ) -> newEqs [MsgEq (m11, m21), MsgEq (m12, m22)] (MEnc m11 m12, MEnc m21 m22 ) -> newEqs [MsgEq (m11, m21), MsgEq (m12, m22)] (MAsymPK m1, MAsymPK m2 ) -> newEqs [MsgEq (m1, m2)] (MAsymSK m1, MAsymSK m2 ) -> newEqs [MsgEq (m1, m2)] (MSymK m11 m12, MSymK m21 m22) -> newEqs [MsgEq (m11, m21), MsgEq (m12, m22)] (m1@(MShrK m11 m12), m2@(MShrK m21 m22)) | m11 == m21 -> newEqs [MsgEq (m12, m22)] | m11 == m22 -> newEqs [MsgEq (m12, m21)] | m12 == m21 -> newEqs [MsgEq (m11, m22)] | m12 == m22 -> newEqs [MsgEq (m11, m21)] | m11 == m12 -> newEqs [MsgEq (m11, m21), MsgEq (m11, m22)] | m21 == m22 -> newEqs [MsgEq (m11, m21), MsgEq (m12, m21)] | (m1, m2) `U.equiv` posteqs -> skipEq | otherwise -> postpone m1 m2 (MConst c1, MConst c2) | c1 == c2 -> skipEq | otherwise -> different "constant" c1 c2 (m1, m2) -> different "message" m1 m2 where skipEq = return ([], eqs , False) newEqs ueqs = return (ueqs, eqs , False) updateSolution eqs' = return ([], eqs', True) noUnifier = fail . ("solve1: " ++) different ty x y = noUnifier $ ty ++ " '" ++ show x ++ "' /= '" ++ show y ++ "'" postpone m1 m2 = return ([], eqs { postEqs = U.equate m1 m2 $ posteqs }, False) elimVarEqVar elim (vl, lhs) (vr, rhs) = case compare vl vr of EQ -> skipEq LT -> elim vr lhs GT -> elim vl rhs mayBeAsymKeys = any (not . mustBeSymKey) -- | Remove the thread identifier equalities. This is logically safe iff there is no fact -- outside the equalities that still refers to the dropped thread identifiers. trimTIDEqs :: Equalities -> ([TID], Equalities) -- ^ Dropped TIDs plus updated equalities trimTIDEqs eqs = (M.keys . tidEqs $ eqs, eqs { tidEqs = M.empty }) -- | Remove the agent identifiers equalities. This is logically safe iff there is no fact -- outside the equalities that still refers to the dropped agent identifiers. trimArbMsgEqs :: Equalities -> ([ArbMsgId], Equalities) -- ^ Dropped ArbMsgIds plus updated equalities trimArbMsgEqs eqs = (M.keys . arbmEqs $ eqs, eqs { arbmEqs = M.empty }) -- | The maximal mapped thread identifier. maxMappedTID :: Equalities -> Maybe TID maxMappedTID = fmap (fst . fst) . M.maxViewWithKey . tidEqs -- | The maximal mapped agent identifier. maxMappedArbMsgId :: Equalities -> Maybe ArbMsgId maxMappedArbMsgId = fmap (fst . fst) . M.maxViewWithKey . arbmEqs -- | Retrieve the role of a thread. threadRole :: TID -> Equalities -> Maybe Role threadRole tid eqs = M.lookup (substTID eqs tid) $ roleEqs eqs -- | Test whether the equalities impose constraints on bound variables only. onlyLocalConstraints :: Equalities -> Bool onlyLocalConstraints eqs = M.null (tidEqs eqs) && M.null (roleEqs eqs) && M.null (avarEqs eqs) && all isBoundVarMsg (map snd $ M.toList $ mvarEqs eqs) && all isBoundVarEq (M.toList $ arbmEqs eqs) && L.null (U.toList (postEqs eqs)) where isBoundVar (BoundVarId _) = True isBoundVar _ = False isBoundVarMsg (MArbMsg v) = isBoundVar v isBoundVarMsg _ = False isBoundVarEq (lhs, rhs) = isBoundVar lhs || isBoundVarMsg rhs -- | Check if an inequality is trivially false, i.e., there exists an -- substitution of bound logical variables which makes it reflexive. falseIneq :: Inequality -> Bool falseIneq ineq = maybe False onlyLocalConstraints $ solve [getInequality ineq] empty ------------------------------------------------------------------------------- -- Abusing equalities to represent mappings of logical variables ------------------------------------------------------------------------------- newtype Mapping = Mapping { getMappingEqs :: Equalities } deriving( Eq, Ord, Show, Data, Typeable ) -- | Map the equalities inside a mapping. mapMapping :: (Equalities -> Equalities) -> Mapping -> Mapping mapMapping f = Mapping . f . getMappingEqs -- | An empty mapping. emptyMapping :: Mapping emptyMapping = Mapping empty -- | A mapping of logical variables and the corresponding substitution can be -- represented as an abstract Equalities value. However, it violates the -- invariant that the domain of the equalities must be invariant under -- substitution. This is OK, as domain and range of a mapping are from -- different logical contexts. mkMapping :: M.Map TID TID -> M.Map ArbMsgId ArbMsgId -> Mapping mkMapping tideqs arbmeqs = Mapping $ empty {tidEqs = tideqs , arbmEqs = M.map MArbMsg arbmeqs} -- | Add a mapping from one thread identifier to another one, possibly -- overriding an existing mapping. addTIDMapping :: TID -> TID -> Mapping -> Mapping addTIDMapping from to = mapMapping $ \eqs -> eqs { tidEqs = M.insert from to $ tidEqs eqs } -- | Add a mapping from one arbitrary-message id to another arbitrary-message -- id, possibly overriding an existing mapping. addArbMsgIdMapping :: ArbMsgId -> ArbMsgId -> Mapping -> Mapping addArbMsgIdMapping from to = mapMapping $ \eqs -> eqs { arbmEqs = M.insert from (MArbMsg to) $ arbmEqs eqs } -- | Add a mapping from one thread identifier to an other role, possibly -- overriding an existing mapping. addTIDRoleMapping :: TID -> Role -> Mapping -> Mapping addTIDRoleMapping tid role = mapMapping $ \eqs -> let tid' = substTID eqs tid in eqs { roleEqs = M.insert tid' role $ roleEqs eqs } -- | Delete the mapping of the given thread identifier. deleteTIDMapping :: TID -> Mapping -> Mapping deleteTIDMapping tid = mapMapping $ \eqs -> eqs { tidEqs = M.delete tid $ tidEqs eqs } -- | Delete the mapping of the given agent identifier. deleteArbMsgIdMapping :: ArbMsgId -> Mapping -> Mapping deleteArbMsgIdMapping aid = mapMapping $ \eqs -> eqs { arbmEqs = M.delete aid $ arbmEqs eqs } ------------------------------------------------------------------------------ -- Pretty Printing ------------------------------------------------------------------------------ -- Helper functions for pretty printing --------------------------------------- ppEq :: Doc -> (a -> Doc) -> (b -> Doc) -> (a, b) -> Doc ppEq sym pp1 pp2 (x1, x2) = pp1 x1 <-> sym <-> pp2 x2 ppEq' :: Doc -> (a -> Doc) -> (a, a) -> Doc ppEq' sym pp = ppEq sym pp pp filterBoundVars :: [ArbMsgId] -> [ArbMsgId] filterBoundVars = filter p where p (BoundVarId _) = True p (FreeVarId _) = False -- Isar ------- ppIsarEq :: Doc -> IsarConf -> AnyEq -> Doc ppIsarEq sym conf eq0 = case eq0 of TIDEq eq -> ppEq' sym ppIsar eq RoleEq eq -> ppEq' sym (text . roleName) eq TIDRoleEq (tid, role) -> text "roleMap r" <-> ppIsar tid <-> sym <-> text ("Some " ++ roleName role) ArbMsgEq eq -> ppEq sym ppIsar ppIsar eq AVarEq eq -> ppEq' sym ppIsar eq MVarEq eq -> ppEq sym ppIsar ppIsar eq MsgEq eq -> ppEq' sym ppIsar eq where ppIsar :: Isar a => a -> Doc ppIsar = isar conf instance Isar AnyEq where isar = ppIsarEq (char '=') instance Isar Inequality where isar conf eq = if L.null boundVars then ineq else parens $ quantifiers <-> ineq where boundVars = map (isar conf) $ filterBoundVars $ inequalityAMIDs eq ineq = ppIsarEq (isaNotEq conf) conf (getInequality eq) quantifiers = foldl (<->) (isaForall conf) boundVars <> char '.' -- SP Theory ------------ ppSPTEq :: Doc -> AnyEq -> Doc ppSPTEq sym eq0 = case eq0 of TIDEq eq -> ppEq' sym sptTID eq RoleEq eq -> ppEq' sym (text . roleName) eq TIDRoleEq (tid, role) -> text "role(" <-> sptTID tid <-> char ')' <-> sym <-> text (roleName role) ArbMsgEq eq -> ppEq sym sptArbMsgId sptMessage eq AVarEq eq -> ppEq' sym sptAVar eq MVarEq eq -> ppEq sym sptMVar sptMessage eq MsgEq eq -> ppEq' sym sptMessage eq sptAnyEq :: AnyEq -> Doc sptAnyEq = ppSPTEq (char '=') sptInequality :: Inequality -> Doc sptInequality eq = if L.null boundVars then ineq else parens $ quantifiers <-> ineq where boundVars = map sptArbMsgId $ filterBoundVars $ inequalityAMIDs eq ineq = ppSPTEq (text "!=") (getInequality eq) quantifiers = foldl (<->) (char '!') boundVars <> char '.' {- -- | Convert the equalities for pretty printing. sptEqualities :: Equalities -> ([Doc], [Doc], [Doc]) -- ^ quantified variables, representable -- equalities, non-representable equalities sptEqualities (Equalities tideqs roleeqs aveqs mveqs arbmeqs) = ( map sptTID (M.keys roleeqs) ++ [ sptArbMsgId aid | (aid, Nothing) <- M.toList arbmeqs] , ppMapMaybe ppTIDRoleEq roleeqs ++ ppVarEqs ppAVar ppAVar aveqs ++ ppVarEqs ppMVar sptMessage mveqs ++ ppVarEqs ppAgent (maybe emptyDoc (either ppAgent ppAVar)) arbmeqs , ppMap ppTIDEq tideqs ++ ppMapMaybe ppArbMsgEq arbmeqs ) where ppAVar = sptMessage . MAVar ppMVar = sptMessage . MMVar ppAgent = sptMessage . MArbMsg ppMap ppElem = map ppElem . M.toList ppMapMaybe ppElem m = map ppElem [(k,v) | (k, Just v) <- M.toList m] ppTIDEq (tid1,tid2) = sptTID tid1 <-> text "->" <-> sptTID tid2 ppTIDRoleEq (tid, role) = text "role(" <> sptTID tid <> text ") =" <-> text (roleName role) ppArbMsgEq (aid, rhs) = sptArbMsgId aid <-> text "->" <-> either sptArbMsgId sptLocalId rhs ppVarEqs dom ran = ppMap ppVarEq . equalityChains where ppVarEq (r,ds) = fsep . intersperse (char '=') $ ran r : map dom (S.toList ds) -- | Compute the equality classes given wrto a partial function. equalityChains :: (Ord a, Ord b) => M.Map a b -> M.Map b (S.Set a) equalityChains = foldl' insertEdge M.empty . M.toList where insertEdge m (from,to) = M.insertWith' S.union to (S.singleton from) m -}