packages feed

cpsa-4.4.3: src/CPSA/Roletran/Loader.hs

-- Loads the first protocol

-- 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.Loader (loadSExprs) where

import Control.Monad (foldM)
import qualified Data.List as L
import qualified Data.Map.Strict as M
import CPSA.Lib.ReturnFail
import CPSA.Lib.SExpr
import CPSA.Roletran.Algebra
import CPSA.Roletran.Protocol

{--
import System.IO.Unsafe
z :: Show a => a -> b -> b
z x y = unsafePerformIO (print x >> return y)

zz :: Show a => a -> a
zz x = z x x
--}

-- Load protocols and preskeletons from a list of S-expressions, and
-- then return a list of preskeletons.  The name of the algebra is
-- nom, and its variable generator is provided.

loadSExprs :: MonadFail m => [SExpr Pos] -> m Prot
loadSExprs ((L pos (S _ "defprotocol" : xs)) : _) =
  loadProt pos xs
loadSExprs (_ : sexprs) =
  loadSExprs sexprs
loadSExprs [] =
  fail "No protocol found"

loadProt :: MonadFail m => Pos -> [SExpr Pos] -> m Prot
loadProt pos (S _ name : S _ alg : x : xs)
    | alg /= "basic"=
        fail (shows pos "Expecting terms in basic algebra ")
    | otherwise =
        do
          rs <- loadRoles (x : xs)
          -- Check for duplicate role names
          validate (mkProt name pos rs) rs
    where
      validate prot [] = return prot
      validate prot (r : rs) =
          case L.find (\r' -> rname r == rname r') rs of
            Nothing -> validate prot rs
            Just _ ->
                let msg = "Duplicate role " ++ rname r ++
                          " in protocol " ++ name in
                fail (shows pos msg)
loadProt pos _ = fail (shows pos "Malformed protocol")

loadRoles :: MonadFail m => [SExpr Pos] -> m [Role]
loadRoles (L pos (S _ "defrole" : x) : xs) =
    do
      r <- loadRole pos x
      rs <- loadRoles xs
      return (r : rs)
loadRoles _ =
    return []

loadRole :: MonadFail m => Pos -> [SExpr Pos] -> m Role
loadRole pos (S _ name :
              L _ (S _ "vars" : vars) :
              L _ (S _ "trace" : evt : c) :
              rest) =
    do
      declEnv <- loadVars vars
      c <- loadTrace declEnv (evt : c)
      case traceWellFormed c of
        Nothing -> fail (shows pos "Malformed terms")
        Just env ->
          do
            u <- mapM (loadBasic env) (assoc "uniq-orig" rest)
            i <- mapM (loadChanTerm env) -- Allow params as a synonym
                 (assoc "inputs" rest ++ assoc "params" rest)
            o <- mapM (loadTerm env) -- Allow returns as a synonym
                 (assoc "outputs" rest ++ assoc "returns" rest)
            mkRole name pos env c u i o
loadRole pos _ = fail (shows pos "Malformed role")

-- Lookup value in alist, appending values with the same key
assoc :: String -> [SExpr a] -> [SExpr a]
assoc key alist =
    concat [ rest | L _ (S _ head : rest) <- alist, key == head ]

loadTrace :: MonadFail m => VarEnv -> [SExpr Pos] -> m Trace
loadTrace env xs = mapM (loadEvt env) xs

loadEvt :: MonadFail m => VarEnv -> SExpr Pos -> m Event
loadEvt env (L pos [S _ "recv", ch, t]) =
    do
      ch <- loadChan env ch
      t <- loadTerm env t
      return (In pos ch t)
loadEvt env (L pos [S _ "send", ch, t]) =
    do
      ch <- loadChan env ch
      t <- loadTerm env t
      return (Out pos ch t)
loadEvt _ (L pos [S _ _, _]) =
    fail (shows pos $ "Missing channel")
loadEvt _ (L pos (S _ dir : _)) =
    fail (shows pos $ "Unrecognized direction " ++ dir)
loadEvt _ x = fail (shows (annotation x) "Malformed event")

-- Variable declarations

loadVars :: MonadFail m => [SExpr Pos] -> m VarEnv
loadVars sexprs =
    do
      pairs <- mapM loadVarPair sexprs
      foldM loadVar emptyVarEnv (concat pairs)

loadVarPair :: MonadFail m => SExpr Pos -> m [(SExpr Pos, SExpr Pos)]
loadVarPair (L _ (x:y:xs)) =
  case reverse (x:y:xs) of
    t : vs ->
      return [(v,t) | v <- reverse vs]
    [] -> error "Loader.loadVarPair: [] cannot happen"
loadVarPair x = fail (shows (annotation x) "Bad variable declaration")

loadVar :: MonadFail m => VarEnv -> (SExpr Pos, SExpr Pos) -> m VarEnv
loadVar env (S pos name, S pos' sort) =
  case M.lookup name env of
    Just _ ->
      fail (shows pos "Duplicate variable declaration for " ++ name)
    Nothing ->
      do
        s <- loadSort pos' sort
        return $ M.insert name s env
loadVar _ (x, _) = fail (shows (annotation x) "Bad variable syntax")

loadSort :: MonadFail m => Pos -> String -> m Sort
loadSort pos sort
  | sort == "text" = return Text
  | sort == "data" = return Data
  | sort == "name" = return Name
  | sort == "skey" = return Skey
  | sort == "akey" = return Akey
  | sort == "mesg" = return Mesg
  | sort == "chan" = return Chan
  | otherwise = fail (shows pos "Sort " ++ sort ++ " not recognized")

-- Term loading

loadChan :: MonadFail m => VarEnv -> SExpr Pos -> m Term
loadChan env (S pos v) =
  case M.lookup v env of
    Nothing -> fail (shows pos ("Undeclared variable " ++ v))
    Just Chan -> return (Chn v)
    Just _ -> fail (shows pos "Expecting a channel")
loadChan _ x =
  fail (shows (annotation x) "Expecting a channel")

loadBasic :: MonadFail m => VarEnv -> SExpr Pos -> m Term
loadBasic env x =
    do
      t <- loadTerm env x
      case isBasic t of
        True -> return t
        False -> fail (shows (annotation x) "Expecting a basic value")

loadLookup :: MonadFail m => VarEnv -> Pos -> Var -> m Term
loadLookup env pos v =
  case M.lookup v env of
    Nothing -> fail (shows pos ("Undeclared variable " ++ v))
    Just Text -> return (Txt v)
    Just Data -> return (Dta v)
    Just Name -> return (Nam v)
    Just Skey -> return (Sky (SVar v))
    Just Akey -> return (Aky (AVar v))
    Just Ikey -> fail (shows pos "Not expecting an ikey")
    Just Mesg -> return (Msg v)
    Just Chan -> fail (shows pos "Not expecting a channel")

loadChanTerm :: MonadFail m => VarEnv -> SExpr Pos -> m Term
loadChanTerm env x =
  case loadChan env x of
    Return t -> return t
    Fail _ -> loadTerm env x

loadName :: MonadFail m => VarEnv -> Pos -> Var -> m ()
loadName env pos v =
  do
    t <- loadLookup env pos v
    case sort t of
      Name -> return ()
      _ -> fail (shows pos "Expecting a name")

loadTerm :: MonadFail m => VarEnv -> SExpr Pos -> m Term
loadTerm env (S pos s) =
  loadLookup env pos s
loadTerm _ (Q _ t) =
  return (Tag t)
loadTerm env (L pos (S _ s : l)) =
  case lookup s loadDispatch of
    Nothing -> fail (shows pos "Keyword " ++ s ++ " unknown")
    Just f -> f pos env l
loadTerm _ x = fail (shows (annotation x) "Malformed term")

type LoadFunction m = Pos -> VarEnv -> [SExpr Pos] -> m Term

loadDispatch :: MonadFail m => [(String, LoadFunction m)]
loadDispatch =
    [("pubk", loadPubk)
    ,("privk", loadPrivk)
    ,("invk", loadInvk)
    ,("ltk", loadLtk)
    ,("cat", loadCat)
    ,("enc", loadEnc)
    ,("hash", loadHash)
    ]

-- Atom constructors: pubk privk invk ltk

loadPubk :: MonadFail m => LoadFunction m
loadPubk _ env [S pos s] =
    do
      loadName env pos s
      return $ Aky (Pubk s)
loadPubk _ env [Q _ c, S pos s] =
    do
      loadName env pos s
      return $ Aky (Pubk2 c s)
loadPubk pos _ _ = fail (shows pos "Malformed pubk")

loadPrivk :: MonadFail m => LoadFunction m
loadPrivk _ env [S pos s] =
    do
      loadName env pos s
      return $ Iky (Pubk s)
loadPrivk _ env [Q _ c, S pos s] =
    do
      loadName env pos s
      return $ Iky (Pubk2 c s)
loadPrivk pos _ _ = fail (shows pos "Malformed privk")

loadInvk :: MonadFail m => LoadFunction m
loadInvk _ env [t] =
    do
      t <- loadTerm env t
      return $ inv t
loadInvk pos _ _ = fail (shows pos "Malformed invk")

loadLtk :: MonadFail m => LoadFunction m
loadLtk _ env [S pos s, S pos' s'] =
    do
      loadName env pos s
      loadName env pos' s'
      return $ Sky (Ltk s s')
loadLtk pos _ _ = fail (shows pos "Malformed ltk")

-- Term constructors: cat enc

loadCat :: MonadFail m => LoadFunction m
loadCat _ env (l : ls) =
    do
      ts <- mapM (loadTerm env) (l : ls)
      return $ foldr1 Pr ts
loadCat pos _ _ = fail (shows pos "Malformed cat")

loadEnc :: MonadFail m => LoadFunction m
loadEnc pos env (l : l' : ls) =
    do
      let (butLast, last) = splitLast l (l' : ls)
      t <- loadCat pos env butLast
      t' <- loadTerm env last
      case isMesgVar t' of
        False -> return $ En t t'
        True -> fail (shows (annotation last)
                      "Message variable cannot used as key")
loadEnc pos _ _ = fail (shows pos "Malformed enc")

splitLast :: a -> [a] -> ([a], a)
splitLast x xs =
    loop [] x xs
    where
      loop z x [] = (reverse z, x)
      loop z x (y : ys) = loop (x : z) y ys

loadHash :: MonadFail m => LoadFunction m
loadHash pos env (l : ls) =
   do
     t <- loadCat pos env (l : ls)
     return $ Hsh t
loadHash pos _ _ = fail (shows pos "Malformed hash")