packages feed

cpsa-4.4.1: src/CPSA/Roletran/Protocol.hs

-- Protocol data structures

-- Copyright (c) 2020 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.Roletran.Protocol (
  Event (..), Trace, traceWellFormed,
  Role, mkRole, rname, rpos, rvars, rtrace, runiques, rinputs, routputs,
  Prot, mkProt, pname, ppos, roles) where

import Control.Monad (foldM)
import qualified Data.List as L
import CPSA.Lib.SExpr
import CPSA.Roletran.Algebra

-- Message events and traces

data Event
    = In Pos !Term !Term        -- Inbound message (channel first)
    | Out Pos !Term !Term       -- Outbound message (channel first)
      deriving Show

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

-- At what position does a term originate in a trace?
originationPos :: Term -> Trace -> Maybe Int
originationPos t c =
    loop 0 c
    where
      loop _ [] = Nothing       -- Term is not carried
      loop pos (Out _ _ t' : c)
          | t `carriedBy` t' = Just pos -- Found it
          | otherwise = loop (pos + 1) c
      loop pos (In _ _ t' : c)
          | t `carriedBy` t' = Nothing -- Term does not originate
          | otherwise = loop (pos + 1) c

eventWellFormed :: VarEnv -> Event -> Maybe VarEnv
eventWellFormed env (In _ ch t) =
  doubleTermWellFormed env ch t
eventWellFormed env (Out _ ch t) =
  doubleTermWellFormed env ch t

traceWellFormed :: Trace -> Maybe VarEnv
traceWellFormed c =
  foldM eventWellFormed emptyVarEnv c

data Role = Role
    { rname :: !String,
      rpos :: !Pos,
      rvars :: !VarEnv,           -- Variables in the trace
      rtrace :: ![Event],         -- Events in causal order
      runiques :: ![(Term, Int)], -- Uniquely originating atoms and positions
      rinputs :: ![Term],         -- Sequence of inputs atoms
      routputs :: ![Term] }       -- Sequence of outputs terms
    deriving Show

{-
The translator performs various checks to ensure that each role read
is well formed.  It ensures the each term in the inputs is a basic
value.  It ensures that any basic value assumed to be uniquely
originating originates in the role's trace, and that no uniquely
originating value is in the inputs.  It ensures that no input variable
is specified as an output.  It also ensures that every variable in
terms that occur in the inputs, outputs, or are assumed to be uniquely
originating also occur in the role's trace.
-}

-- Create a role
mkRole :: MonadFail m => String -> Pos -> VarEnv -> Trace ->
          [Term] -> [Term] -> [Term] -> m Role
mkRole name pos vars trace uniques inputs outputs =
  do
    mapM_ (checkInput pos) inputs
    mapM_ (checkUniq pos inputs) uniques
    uniquesPos <- mapM (addPos pos trace) (L.nub uniques)
    mapM_ (checkOutput pos inputs) outputs
    return $ Role
      { rname = name,
        rpos = pos,
        rvars = vars,
        rtrace = trace,
        runiques = uniquesPos,
        rinputs = L.nub inputs,
        routputs = outputs
      }

checkInput :: MonadFail m => Pos -> Term -> m ()
checkInput _ input | isBasic input || isChanVar input = return ()
checkInput pos input =
    fail (shows pos $ "Input is not a basic value or a channel " ++ show input)

checkUniq :: MonadFail m => Pos -> [Term] -> Term -> m ()
checkUniq pos inputs unique
  | elem unique inputs =
      fail (shows pos $ "Uniquely originating value in input " ++ show unique)
  | otherwise = return ()

addPos :: MonadFail m => Pos -> Trace -> Term -> m (Term, Int)
addPos pos trace unique =
  case originationPos unique trace of
    Just i -> return (unique, i)
    Nothing -> fail (shows pos $ "Uniquely originating value " ++
                     show unique ++ " does not originate in trace")

checkOutput :: MonadFail m => Pos -> [Term] -> Term -> m ()
checkOutput pos inputs output
  | elem output inputs =
      fail (shows pos $ "Output contains an input value " ++ show output)
  | otherwise = return ()

-- Protocols

data Prot
    = Prot { pname :: !String,  -- Name of the protocol
             ppos :: !Pos,      -- Source position of the protocol
             roles :: ![Role] } -- Non-listener roles of a protocol
    deriving Show

-- Callers should ensure every role has a distinct name.
mkProt :: String -> Pos -> [Role] -> Prot
mkProt name pos roles =
    Prot { pname = name, ppos = pos, roles = roles }