cpsa-3.6.6: src/CPSA/Prot/Main.hs
-- Translate protocols in Alice and Bob notation into defprotocol syntax
-- 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.
{-
This program translates a protocol expressed in Alice and Bob notation
into defprotocol syntax. It translates defprot forms, and passes all
else through unchanged. The syntax of a defprot form is:
PROT ::= (defprot NAME ALGEBRA (vars DECLS) NOTATION* RULE* PROT-ALIST)
NOTATION ::= (msg ROLE ROLE CHMSG)
| (msg ROLE ROLE CHMSG CHMSG)
| (from ROLE CHMSG)
| (to ROLE CHMSG)
| (clone ROLE ROLE)
| (assume ROLE ASSUMPTION*)
CHMSG ::= (chmsg CHAN TERM) | (chmsg TERM) | TERM
ASSUMPTION ::= (uniq-orig ...) | (non-orig ...) | ...
RULE ::= (defrule ...)
The form (msg A B M) says role A sends M, and role B receives M.
The form (msg A B M N) says role A sends M, and role B receives N.
The form (from A M) says role A sends M.
The form (to A M) says role A receives M.
The form (clone A B) says make role B have the same messages as A up
to this point in the specification.
-}
{-# LANGUAGE CPP #-}
#if !(MIN_VERSION_base(4,13,0))
#define MonadFail Monad
#endif
module Main (main) where
import Numeric
import System.IO
import System.Console.GetOpt
import Control.Monad (foldM)
import CPSA.Lib.SExpr
import CPSA.Lib.Printer (pp)
import CPSA.Lib.Entry
import CPSA.Lib.Expand (readSExprs, expand)
-- Runtime parameters
data Params = Params
{ file :: Maybe FilePath, -- Nothing specifies standard output
margin :: Int, -- Output line length
doExpand :: Bool} -- Expand macros first?
deriving Show
defaultIndent :: Int
defaultIndent = optIndent defaultOptions
defaultMargin :: Int
defaultMargin = optMargin defaultOptions
main :: IO ()
main =
do
(p, params) <- start options interp
sexprs <- readSExprs p
-- Could expand macros here
sexprs <- tryAbort (maybeExpand (doExpand params) sexprs)
sexprs <- tryAbort (mapM translate sexprs)
h <- outputHandle $ file params
mapM_ (display (margin params) h) sexprs
hClose h
maybeExpand :: Bool -> [SExpr Pos] -> IO [SExpr Pos]
maybeExpand False sexprs = return sexprs
maybeExpand True sexprs = expand sexprs
tryAbort :: IO a -> IO a
tryAbort x =
do
a <- tryIO x
case a of
Left err -> abort err
Right a -> return a
display :: Int -> Handle -> SExpr a -> IO ()
display margin h x =
do
hPutStrLn h $ pp margin defaultIndent x
hPutStrLn h ""
-- Command line option flags
data Flag
= Help -- Help
| Info -- Version information
| Margin String -- Output line length
| Output String -- Output file name
| Expand -- Expand macros
deriving Show
options :: [OptDescr Flag]
options =
[ Option ['o'] ["output"] (ReqArg Output "FILE") "output FILE",
Option ['m'] ["margin"] (ReqArg Margin "INT")
("set output margin (default " ++ show defaultMargin ++ ")"),
Option ['e'] ["expand"] (NoArg Expand) "expand macros first",
Option ['h'] ["help"] (NoArg Help) "show help message",
Option ['v'] ["version"] (NoArg Info) "show version number"]
-- Interpret option flags
interp :: [Flag] -> IO Params
interp flags =
loop flags (Params { file = Nothing, -- By default, no output file
margin = defaultMargin, doExpand = False})
where
loop [] params = return params
loop (Expand : flags) params =
loop flags $ params { doExpand = True }
loop (Output name : flags) params
| file params == Nothing =
loop flags $ params { file = Just name }
loop (Margin value : flags) params =
case readDec value of
[(margin, "")] ->
loop flags $ params { margin = margin }
_ ->
do
msg <- usage options ["Bad value for margin\n"]
abort msg
loop (Info : _) _ =
success cpsaVersion
loop (Help : _) _ =
do -- Show help then exit with success
msg <- usage options []
success msg
loop _ _ =
do -- Show help then exit with failure
msg <- usage options ["Bad option combination\n"]
abort msg
-- Translate one S-expression
translate :: MonadFail m => SExpr Pos -> m (SExpr ())
translate (L pos (S _ "defprot" : xs)) =
prot pos xs
translate x = return $ strip x
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)
prot :: MonadFail m => Pos -> [SExpr Pos] -> m (SExpr ())
prot _ (S _ name : S _ alg : L _ (S _ "vars" : vs) : body) =
do
let vars = L () (S () "vars" : map strip vs)
rs <- roles vars body
return
(L () (S () "defprotocol" : S () name :
S () alg : rs ++ filter scrub (map strip body)))
prot p _ = fail (shows p "Malformed defprot")
roles :: MonadFail m => SExpr () -> [SExpr Pos] -> m [SExpr ()]
roles vars body =
do
env <- foldM msg [] body
assumes <- foldM assume [] body
return $ map (makeRole vars assumes) env
makeRole :: SExpr () -> Env -> (String, [SExpr ()]) -> SExpr ()
makeRole vars assumes (name, trace) =
L () (S () "defrole" : S () name : vars :
L () (S () "trace" : reverse trace) :
case lookup name assumes of
Nothing -> []
Just xs -> reverse xs)
type Env = [(String, [SExpr ()])]
msg :: MonadFail m => Env -> SExpr Pos -> m Env
msg env (L _ [S _ "msg", S _ from, S _ to, term]) =
return (update to (L () (S () "recv" : chmsg term))
(update from (L () (S () "send" : chmsg term)) env))
msg env (L _ [S _ "msg", S _ from, S _ to, fromTerm, toTerm]) =
return (update to (L () (S () "recv" : chmsg toTerm))
(update from (L () (S () "send" : chmsg fromTerm)) env))
msg _ (L p (S _ "msg" : _)) =
fail (shows p "Malformed msg")
msg env (L _ [S _ "from", S _ from, term]) =
return (update from (L () (S () "send" : chmsg term)) env)
msg _ (L p (S _ "from" : _)) =
fail (shows p "Malformed from")
msg env (L _ [S _ "init", S _ from, term]) =
return (update from (L () (S () "init" : chmsg term)) env)
msg _ (L p (S _ "init" : _)) =
fail (shows p "Malformed init")
msg env (L _ [S _ "obsv", S _ from, term]) =
return (update from (L () (S () "obsv" : chmsg term)) env)
msg _ (L p (S _ "obsv" : _)) =
fail (shows p "Malformed obsv")
msg env (L _ [S _ "tran", S _ from, term1, term2]) =
return (update from (L () (S () "tran" : [strip term1, strip term2])) env)
msg _ (L p (S _ "tran" : _)) =
fail (shows p "Malformed tran")
msg env (L _ [S _ "to", S _ to, term]) =
return (update to (L () (S () "recv" : chmsg term)) env)
msg _ (L p (S _ "to" : _)) =
fail (shows p "Malformed to")
msg env (L _ [S _ "clone", S p1 from, S p2 to]) =
case lookup to env of
Just _ -> fail (shows p2 ("Role " ++ to ++ " already defined"))
Nothing ->
case lookup from env of
Nothing -> fail (shows p1 ("Role " ++ from ++ " not defined yet"))
Just trace -> return $ (to, trace) : env
msg _ (L p (S _ "clone" : _)) =
fail (shows p "Malformed clone")
msg env _ = return env
chmsg :: SExpr a -> [SExpr ()]
chmsg (L _ [S _ "chmsg", S _ chan, term]) =
[S () chan, strip term]
chmsg (L _ [S _ "chmsg", term]) = [strip term]
chmsg term = [strip term]
update :: String -> SExpr () -> Env -> Env
update role msg [] = [(role, [msg])]
update role msg ((name, trace) : rest)
| role == name = (name, msg : trace) : rest
| otherwise = (name, trace) : update role msg rest
assume :: MonadFail m => Env -> SExpr Pos -> m Env
assume env (L _ (S _ "assume" : S _ role : xs)) =
return $ augment role (map strip xs) env
assume _ (L p (S _ "assume" : _)) =
fail (shows p "Malformed assume")
assume env _ = return env
augment :: String -> [SExpr ()] -> Env -> Env
augment role assumes [] = [(role, reverse assumes)]
augment role assumes ((name, a) : rest)
| role == name = (name, reverse assumes ++ a) : rest
| otherwise = (name, a) : augment role assumes rest
scrub :: SExpr () -> Bool
scrub (L () (S () "msg" : _)) = False
scrub (L () (S () "from" : _)) = False
scrub (L () (S () "to" : _)) = False
scrub (L () (S () "clone" : _)) = False
scrub (L () (S () "assume" : _)) = False
scrub (L () (S () "init" : _)) = False
scrub (L () (S () "obsv" : _)) = False
scrub (L () (S () "tran" : _)) = False
scrub _ = True