cpsa-2.2.9: src/CPSA/Lib/Main.hs
-- Main routine for the CPSA solver. Provides command line processing
-- 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 Main (main) where
import Numeric
import Control.Monad (forM_)
import System.IO
import System.IO.Error
import System.Environment
import System.Console.GetOpt
import CPSA.Lib.SExpr
import CPSA.Lib.Entry
import CPSA.Lib.Algebra
import CPSA.Lib.Loader
import CPSA.Lib.Expand
import CPSA.Lib.Reduction
import qualified CPSA.Basic.Algebra
import qualified CPSA.DiffieHellman.Algebra
import qualified CPSA.DiffieHellmanNoReciprocal.Algebra
import qualified CPSA.SimpleDiffieHellman.Algebra
-- Default limit on the number of steps used to solve one problem.
defaultStepLimit :: Int
defaultStepLimit = optLimit defaultOptions
-- Default limit on the number of strands is a skeleton.
defaultStrandBound :: Int
defaultStrandBound = optBound defaultOptions
-- Default algebra
defaultAlgebra :: String
defaultAlgebra = optAlg defaultOptions
-- Entry point
main :: IO ()
main =
do
argv <- getArgs
(flags, files) <- opts options argv
-- Handle help and version options before input is read
_ <- interp algs defaultOptions flags
p <- openInput files
sexprs <- readSExprs p
let herald = getHerald sexprs
alist <- case herald of
Nothing -> return []
Just sexpr -> getAlist sexpr
heraldFlags <- getAlistOpts alist
forM_ heraldFlags checkHeraldFlag
opts <- interp algs defaultOptions heraldFlags
opts <- interp algs opts flags
sexprs <- try (expand sexprs) -- Expand macros
case sexprs of
Left err -> abort (ioeGetErrorString err)
Right sexprs ->
if optAnalyze opts then
select herald opts sexprs
else
prettyPrint opts sexprs
opts :: [OptDescr a] -> [String] -> IO ([a], [String])
opts options argv =
case getOpt RequireOrder options argv of
(o, n, []) -> return (o, n)
(_, _, errs) ->
do
msg <- usage options errs
abort msg
openInput :: [String] -> IO PosHandle
openInput [file] =
do -- Input from named file
input <- openFile file ReadMode
posHandle file input
openInput [] =
posHandle "" stdin -- Input from the standard input
openInput _ =
do
msg <- usage options ["too many input files\n"]
abort msg
readSExprs :: PosHandle -> IO [SExpr Pos]
readSExprs p =
loop []
where
loop xs =
do
x <- readSExpr p
case x of
Nothing ->
return $ reverse xs
Just x ->
loop (x:xs)
-- Algebra specific section
-- Algebra names
algs :: [String]
algs = [CPSA.Basic.Algebra.name, CPSA.DiffieHellman.Algebra.name,
CPSA.DiffieHellmanNoReciprocal.Algebra.name,
CPSA.SimpleDiffieHellman.Algebra.name]
-- Select the algebra and go.
select :: Maybe (SExpr Pos) -> Options -> [SExpr Pos] -> IO ()
select herald opts sexprs =
case optAlg opts of
name | name == CPSA.Basic.Algebra.name ->
go name CPSA.Basic.Algebra.origin
herald opts sexprs
| name == CPSA.DiffieHellman.Algebra.name ->
go name CPSA.DiffieHellman.Algebra.origin
herald opts sexprs
| name == CPSA.DiffieHellmanNoReciprocal.Algebra.name ->
go name CPSA.DiffieHellmanNoReciprocal.Algebra.origin
herald opts sexprs
| name == CPSA.SimpleDiffieHellman.Algebra.name ->
go name CPSA.SimpleDiffieHellman.Algebra.origin
herald opts sexprs
| otherwise ->
abort ("Bad algebra: " ++ name)
-- Load protocols and preskeletons and print run time information
go :: Algebra t p g s e c => String -> g -> Maybe (SExpr Pos) ->
Options -> [SExpr Pos] -> IO ()
go name origin herald opts sexprs =
do
preskels <- try (loadSExprs name origin sexprs)
case preskels of -- Load protocols and preskeletons
Left err -> abort (ioeGetErrorString err)
Right preskels ->
do
h <- outputHandle (optFile opts)
let m = optMargin opts
-- Print herald
case herald of
Nothing -> return ()
Just sexpr ->
do
writeSExpr h m sexpr
hPutStrLn h ""
-- Print run time information
writeComment h m cpsaVersion
writeComment h m "All input read"
case optNoIsoChk opts of
True -> writeComment h m "Isomorphism checking disabled"
False -> return ()
case optLimit opts /= defaultStepLimit of
True -> writeComment h m $
"Step count limited to " ++ show (optLimit opts)
False -> return ()
case optBound opts /= defaultStrandBound of
True -> writeComment h m $
"Strand count bounded at " ++ show (optBound opts)
False -> return ()
case optCheckNoncesFirst opts of
True -> writeComment h m "Nonces checked first"
False -> return ()
case optTryOldStrandsFirst opts of
True -> writeComment h m "Old strands tried first"
False -> return ()
case optTryYoungNodesFirst opts of
True -> writeComment h m "Younger nodes tried first"
False -> return ()
-- Analyze
solve opts h preskels 0
-- Just pretty the expanded macros
prettyPrint :: Options -> [SExpr a] -> IO ()
prettyPrint opts sexprs =
do
let m = optMargin opts
h <- outputHandle (optFile opts)
writeComment h m cpsaVersion
writeComment h m "Expanded macros"
mapM_ (writeLnSEexpr h m) sexprs
hClose h
return ()
-- Command line option flags
data Flag
= Output String -- Output file name
| Limit String -- Step count limit
| Bound String -- Strand count bound
| Margin String -- Output line length
| Expand -- Expand macros only
| NoIsoChk -- Disable isomorphism checks
| CheckNoncesFirst -- Check nonces first
| TryOldStrandsFirst -- Try old strands first
| TryYoungNodesFirst -- Try young nodes first
| Algebra String -- Algebra
| Algebras -- Show algebras
| Help -- Help
| Info -- Version information
deriving Show
options :: [OptDescr Flag]
options =
[ Option ['o'] ["output"] (ReqArg Output "FILE") "output FILE",
Option ['l'] ["limit"] (ReqArg Limit "INT")
("step count limit (default " ++ show defaultStepLimit ++ ")"),
Option ['b'] ["bound"] (ReqArg Bound "INT")
("strand count bound (default " ++ show defaultStrandBound ++ ")"),
Option ['m'] ["margin"] (ReqArg Margin "INT")
("set output margin (default " ++ show (optMargin defaultOptions) ++ ")"),
Option ['e'] ["expand"] (NoArg Expand)
"expand macros only; don't analyze",
Option ['n'] ["noisochk"] (NoArg NoIsoChk)
"disable isomorphism checks",
Option ['c'] ["check-nonces"] (NoArg CheckNoncesFirst)
"check nonces first",
Option ['t'] ["try-old-strands"] (NoArg TryOldStrandsFirst)
"try old strands first",
Option ['r'] ["reverse-nodes"] (NoArg TryYoungNodesFirst)
"try younger nodes first",
Option ['a'] ["algebra"] (ReqArg Algebra "STRING")
("algebra (default " ++ defaultAlgebra ++ ")"),
Option ['s'] ["show-algebras"] (NoArg Algebras) "show algebras",
Option ['h'] ["help"] (NoArg Help) "show help message",
Option ['v'] ["version"] (NoArg Info) "show version number" ]
-- Interpret option flags
interp :: [String] -> Options -> [Flag] -> IO Options
interp algs opts flags =
loop flags opts
where
loop [] opts = return opts
loop (Output name : flags) opts
| optFile opts == Nothing =
loop flags $ opts { optFile = Just name }
loop (Limit value : flags) opts =
case readDec value of
[(limit, "")] ->
loop flags $ opts { optLimit = limit }
_ ->
do
msg <- usage options ["Bad value for step limit\n"]
abort msg
loop (Bound value : flags) opts =
case readDec value of
[(bound, "")] ->
loop flags $ opts { optBound = bound }
_ ->
do
msg <- usage options ["Bad value for strand bound\n"]
abort msg
loop (Margin value : flags) opts =
case readDec value of
[(margin, "")] ->
loop flags $ opts { optMargin = margin }
_ ->
do
msg <- usage options ["Bad value for margin\n"]
abort msg
loop (Expand : flags) opts =
loop flags $ opts { optAnalyze = False }
loop (NoIsoChk : flags) opts =
loop flags $ opts { optNoIsoChk = True }
loop (CheckNoncesFirst : flags) opts =
loop flags $ opts { optCheckNoncesFirst = True }
loop (TryOldStrandsFirst : flags) opts =
loop flags $ opts { optTryOldStrandsFirst = True }
loop (TryYoungNodesFirst : flags) opts =
loop flags $ opts { optTryYoungNodesFirst = True }
loop (Algebra name : flags) opts
| elem name algs = loop flags $ opts { optAlg = name }
| otherwise =
abort ("Algebra " ++ name ++ " not one of\n" ++ unlines algs)
loop (Algebras : _) _ =
success $ unlines algs
loop (Help : _) _ =
do -- Show help then exit with success
msg <- usage options []
success msg
loop (Info : _) _ =
success cpsaVersion
loop _ _ =
do -- Show help then exit with failure
msg <- usage options ["Bad option combination\n"]
abort msg
-- Herald form
-- Get a herald form. It must be the first form while skipping comments.
getHerald :: [SExpr Pos] -> Maybe (SExpr Pos)
getHerald (x@(L _ (S _ "herald" : _)) : _) = Just x
getHerald ((L _ (S _ "comment" : _)) : sexprs) = getHerald sexprs
getHerald _ = Nothing
-- Get the association list of a herald form
getAlist :: SExpr Pos -> IO [SExpr Pos]
getAlist (L _ (S _ "herald" : S _ _ : alist)) = return alist
getAlist (L _ (S _ "herald" : Q _ _ : alist)) = return alist
getAlist _ =
do
msg <- usage options ["Bad herald form\n"]
abort msg
getAlistOpts :: [SExpr Pos] -> IO [Flag]
getAlistOpts alist =
loop alist []
where
loop [] flags =
return $ reverse flags
loop ((L _ (S _ key : vals)) : alist) flags =
do
flag <- flagOf key vals
case flag of
Nothing -> loop alist flags -- Ignore unrecognized keys
Just flag -> loop alist (flag : flags)
loop _ _ =
do
msg <- usage options ["Bad herald form\n"]
abort msg
flagOf :: String -> [SExpr Pos] -> IO (Maybe Flag)
flagOf key vals =
do
let opt = findOpt key
case opt of
Nothing -> return Nothing
Just opt ->
do
flag <- interpAssoc key opt vals
return $ Just flag
findOpt :: String -> Maybe (OptDescr Flag)
findOpt key =
loop options
where
loop [] = Nothing
loop (opt@(Option _ keys _ _) : options)
| elem key keys = Just opt
| otherwise = loop options
interpAssoc :: String -> OptDescr Flag -> [SExpr Pos] -> IO Flag
interpAssoc _ (Option _ _ (NoArg flag) _) [] =
return flag
interpAssoc _ (Option _ _ (ReqArg flag _) _) [S _ val] =
return $ flag val
interpAssoc _ (Option _ _ (ReqArg flag _) _) [Q _ val] =
return $ flag val
interpAssoc _ (Option _ _ (ReqArg flag _) _) [N _ val] =
return $ flag $ show val
interpAssoc _ (Option _ _ (OptArg flag _) _) [] =
return $ flag Nothing
interpAssoc _ (Option _ _ (OptArg flag _) _) [S _ val] =
return $ flag $ Just val
interpAssoc _ (Option _ _ (OptArg flag _) _) [Q _ val] =
return $ flag $ Just val
interpAssoc _ (Option _ _ (OptArg flag _) _) [N _ val] =
return $ flag $ Just $ show val
interpAssoc key _ _ =
do
msg <- usage options ["Bad herald form at " ++ key ++ "\n"]
abort msg
checkHeraldFlag :: Flag -> IO ()
checkHeraldFlag (Output _) =
abort "output option not allowed in herald"
checkHeraldFlag Help =
abort "help option not allowed in herald"
checkHeraldFlag Info =
abort "version option not allowed in herald"
checkHeraldFlag Algebras =
abort "show algebras help option not allowed in herald"
checkHeraldFlag _ =
return ()