{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
-- | The actual messages being sent and received over the network are always
-- ground. Here we develop a representation of the denotations of actual
-- messages that may occur during a security proof. This involves symbolically
-- instantiated variables and symbolically inverted keys.
module Scyther.Message (
-- * Messages
    TID(..)
  , LocalId(..)
  , Fresh(..)
  , AVar(..)
  , MVar(..)
  , ArbMsgId(..)
  , Message(..)

  -- ** Queries
  , lidId
  , lidTID
  , avarTID
  , mvarTID
  , msgFMV
  , msgFresh
  , msgAMIDs
  , msgTIDs
  , trivial
  , submessages
  , messageparts
  , mustBeSymKey

  -- ** Construction/Transformation
  , mapFresh
  , mapAVar
  , mapMVar
  , inst
  , instVar
  , normMsg
  , splitNonTrivial

-- * Output
  , sptTID
  , sptArbMsgId
  , sptFresh
  , sptAVar
  , sptMVar
  , sptMessage
) where

import Control.Monad
import Control.Monad.State
import Control.Applicative

import Data.Data

-- workaround new Monoid operator <>
#if __GLASGOW_HASKELL__ < 704
import Data.Monoid
#else
import Data.Monoid hiding ((<>))
#endif

import qualified Data.Set as S

import Text.Isar

import Scyther.Protocol

------------------------------------------------------------------------------
-- Messages
------------------------------------------------------------------------------

-- | A logical variable for a thread identifier. Depending on their context
-- they are either universally or existentially quantified.
newtype TID = TID { getTID :: Int }
  deriving( Eq, Ord, Enum, Num, Data, Typeable {-! NFData !-})

instance Show TID where
  show (TID tid) = '#':show tid

-- | A logical message variable. There are two namespaces: Free variables have
-- global scope. Bound variables are used for inequalities with implicit local
-- quantification.
data ArbMsgId = FreeVarId !Int
              | BoundVarId !Int
  deriving( Eq, Ord, Data, Typeable )

instance Show ArbMsgId where
  show (FreeVarId aid)  = 'a':show aid
  show (BoundVarId bid) = 'b':show bid

-- | A local identifier.
newtype LocalId = LocalId { getLocalId :: (Id, TID) }
  deriving( Eq, Ord, Data, Typeable )

instance Show LocalId where
  show (LocalId (i, tid)) = show i ++ show tid

-- | An agent variable.
newtype AVar = AVar { getAVar :: LocalId }
  deriving( Eq, Ord, Show, Data, Typeable )

-- | A message variable.
newtype MVar = MVar { getMVar :: LocalId }
  deriving( Eq, Ord, Show, Data, Typeable )

-- | A fresh message.
newtype Fresh = Fresh { getFresh :: LocalId }
  deriving( Eq, Ord, Show, Data, Typeable )


-- | Denotations of messages as they occurr during reasoning. Note that we do
-- not model agents, as in the proofs that we want to do no actual agent
-- reference will be needed.
--
-- Note: This is /no free algebra/ due to the nested equalities on thread
-- identifiers and the key-inversion function. However, there is still a most
-- general unifier. The easiest way to understand these messages is to map
-- them to the corresponding Isabelle proof states.
data Message =
    MConst  Id          -- ^ A global constant.
  | MFresh  Fresh       -- ^ A freshly generated message.
  | MAVar   AVar        -- ^ A symbolically instantiated agent variable.
  | MMVar   MVar        -- ^ A symbolically instantiated message variable;
                        --   @MVar (LocalId (Id \"v\", TID 1))@ corresponds to @s(|MV ''v'' tid1|)@.
  | MArbMsg ArbMsgId    -- ^ An arbitrary message; i.e., a logical message variable.
  | MHash   Message            -- ^ Hashing
  | MTup    Message Message    -- ^ Pairing
  | MEnc    Message Message    -- ^ Encryption or signing depending on the key (the second argument)
  | MSymK   Message Message    -- ^ A long-term uni-directional symmetric key
  | MShrK   Message Message    -- ^ A long-term bi-directional symmetric key.
  | MAsymPK Message            -- ^ A long-term asymmetric public key.
  | MAsymSK Message            -- ^ A long-term asymmetric private key.
  | MInvKey Message            -- ^ An application of the key inversion function.
  deriving( Eq, Ord, Show, Data, Typeable {-! NFData !-} )

-- Queries
----------

-- | Identifier of a local id.
lidId :: LocalId -> Id
lidId = fst . getLocalId

-- | Thread identifier of a local id.
lidTID :: LocalId -> TID
lidTID = snd . getLocalId

-- | The thread corresponding to an agent variable
avarTID :: AVar -> TID
avarTID = snd . getLocalId . getAVar

-- | The thread corresponding to an message variable
mvarTID :: MVar -> TID
mvarTID = snd . getLocalId . getMVar

-- | Thread identifiers of a message.
msgTIDs :: Message -> [TID]
msgTIDs (MConst _)    = empty
msgTIDs (MFresh f)    = pure . lidTID . getFresh $ f
msgTIDs (MAVar v)     = pure . avarTID $ v
msgTIDs (MMVar v)     = pure . mvarTID $ v
msgTIDs (MArbMsg _)   = empty
msgTIDs (MHash m)     = msgTIDs m
msgTIDs (MTup m1 m2)  = msgTIDs m1 `mappend` msgTIDs m2
msgTIDs (MEnc m1 m2)  = msgTIDs m1 `mappend` msgTIDs m2
msgTIDs (MSymK m1 m2) = msgTIDs m1 `mappend` msgTIDs m2
msgTIDs (MShrK m1 m2) = msgTIDs m1 `mappend` msgTIDs m2
msgTIDs (MAsymPK m)   = msgTIDs m
msgTIDs (MAsymSK m)   = msgTIDs m
msgTIDs (MInvKey m)   = msgTIDs m

-- | Arbitrary-message identifiers of a message (logical message variables).
msgAMIDs :: Message -> [ArbMsgId]
msgAMIDs (MConst _)    = empty
msgAMIDs (MFresh _)    = empty
msgAMIDs (MAVar _)     = empty
msgAMIDs (MMVar _)     = empty
msgAMIDs (MArbMsg a)   = pure a
msgAMIDs (MHash m)     = msgAMIDs m
msgAMIDs (MTup m1 m2)  = msgAMIDs m1 `mappend` msgAMIDs m2
msgAMIDs (MEnc m1 m2)  = msgAMIDs m1 `mappend` msgAMIDs m2
msgAMIDs (MSymK m1 m2) = msgAMIDs m1 `mappend` msgAMIDs m2
msgAMIDs (MShrK m1 m2) = msgAMIDs m1 `mappend` msgAMIDs m2
msgAMIDs (MAsymPK m)   = msgAMIDs m
msgAMIDs (MAsymSK m)   = msgAMIDs m
msgAMIDs (MInvKey m)   = msgAMIDs m

-- | Free message variables of a message.
msgFMV :: Message -> [MVar]
msgFMV (MMVar v)     = pure v
msgFMV (MHash m)     = msgFMV m
msgFMV (MTup m1 m2)  = msgFMV m1 <|> msgFMV m2
msgFMV (MEnc m1 m2)  = msgFMV m1 <|> msgFMV m2
msgFMV (MSymK m1 m2) = msgFMV m1 <|> msgFMV m2
msgFMV (MShrK m1 m2) = msgFMV m1 <|> msgFMV m2
msgFMV (MAsymPK m)   = msgFMV m
msgFMV (MAsymSK m)   = msgFMV m
msgFMV (MInvKey m)   = msgFMV m
msgFMV _             = empty

-- | Fresh messages of a message.
msgFresh :: Message -> [Fresh]
msgFresh (MFresh lid)  = pure lid
msgFresh (MHash m)     = msgFresh m
msgFresh (MTup m1 m2)  = msgFresh m1 <|> msgFresh m2
msgFresh (MEnc m1 m2)  = msgFresh m1 <|> msgFresh m2
msgFresh (MSymK m1 m2) = msgFresh m1 <|> msgFresh m2
msgFresh (MShrK m1 m2) = msgFresh m1 <|> msgFresh m2
msgFresh (MAsymPK m)   = msgFresh m
msgFresh (MAsymSK m)   = msgFresh m
msgFresh (MInvKey m)   = msgFresh m
msgFresh _             = empty

-- | A message is trivial iff it is a tuple or it is guaranteed to be in the
-- initial intruder knowledge (i.e., global constants and agent variables).
--
-- PRE: Message must be normalized.
trivial :: Message -> Bool
trivial (MConst _) = True
trivial (MAVar _)  = True
trivial (MTup _ _) = True
trivial _          = False

-- | The submessages of message.
submessages :: Message -> S.Set Message
submessages m@(MHash m1)     = S.insert m $ submessages m1
submessages m@(MTup m1 m2)   = S.insert m $ submessages m1 `S.union` submessages m2
submessages m@(MEnc m1 m2)   = S.insert m $ submessages m1 `S.union` submessages m2
submessages m@(MSymK m1 m2)  = S.insert m $ submessages m1 `S.union` submessages m2
submessages m@(MShrK m1 m2)  = S.insert m $ submessages m1 `S.union` submessages m2
submessages m@(MAsymPK m1)   = S.insert m $ submessages m1
submessages m@(MAsymSK m1)   = S.insert m $ submessages m1
submessages   (MInvKey _)    = error "submessages: undefined for key inversion"
submessages m                = S.singleton m

-- | The accessible submessages of message.
messageparts :: Message -> S.Set Message
messageparts m@(MTup m1 m2)   = S.insert m $ messageparts m1 `S.union` messageparts m2
messageparts m@(MEnc m1 m2)   = S.insert m $ messageparts m1 `S.union` messageparts m2
messageparts m                = S.singleton m

-- | True if no ground instance of the message can be an asymmetric key.
mustBeSymKey :: Message -> Bool
mustBeSymKey (MMVar _)   = False
mustBeSymKey (MArbMsg _) = False
mustBeSymKey (MAsymSK _) = False
mustBeSymKey (MAsymPK _) = False
mustBeSymKey (MInvKey m) = mustBeSymKey m
mustBeSymKey _           = True


-- Construction/Transformaiton
------------------------------

mapFresh :: (LocalId -> LocalId) -> Fresh -> Fresh
mapFresh f = Fresh . f . getFresh

mapAVar :: (LocalId -> LocalId) -> AVar -> AVar
mapAVar f = AVar . f . getAVar

mapMVar :: (LocalId -> LocalId) -> MVar -> MVar
mapMVar f = MVar . f . getMVar

-- | Instantiate a pattern to a message. Variables are instantiated
-- symbolically. The resulting message is guaranteed to be normalized w.r.t
-- `normMsg`.
inst :: TID -> Pattern -> Message
inst tid pattern = evalState (go pattern) 0
  where
    go :: Pattern -> State Int Message
    go (PConst i)       = return $ MConst i
    go (PFresh i)       = return $ MFresh (Fresh (LocalId (i, tid)))
    go (PAVar i)        = return $ MAVar  (AVar (LocalId (i, tid)))
    go (PMVar i)        = return $ MMVar  (MVar (LocalId (i, tid)))
    go (PHash pt)       = MHash <$> go pt
    go (PTup pt1 pt2)   = MTup <$> go pt1 <*> go pt2
    go (PEnc pt1 pt2)   = MEnc <$> go pt1 <*> go pt2
    go (PSign pt1 pt2)  = do m1 <- go pt1
                             m2 <- go pt2
                             return $ MTup m1 (MEnc m1 (normMsg $ MInvKey m2))
    go (PSymK pt1 pt2)  = MSymK <$> go pt1 <*> go pt2
    go (PShrK pt1 pt2)  = MShrK <$> go pt1 <*> go pt2
    go (PAsymPK pt)     = MAsymPK <$> go pt
    go (PAsymSK pt)     = MAsymSK <$> go pt
    go (PAny)           = do bid <- get
                             put (bid + 1)
                             return $ MArbMsg (BoundVarId bid)

-- | Instantiate a generic specification variable.
instVar :: TID -> VarId -> Message
instVar tid (SAVar i) = MAVar (AVar (LocalId (i, tid)))
instVar tid (SMVar i) = MMVar (MVar (LocalId (i, tid)))

-- | Normalize a message; i.e., apply key-inversion if possible and swap shared
-- key arguments, if required.
normMsg :: Message -> Message
normMsg m@(MConst _)          = m
normMsg m@(MFresh _)          = m
normMsg m@(MAVar _)           = m
normMsg m@(MMVar _)           = m
normMsg m@(MArbMsg _)         = m
normMsg (MInvKey (MInvKey m)) = normMsg m
normMsg (MInvKey (MAsymPK m)) = MAsymSK (normMsg m)
normMsg (MInvKey (MAsymSK m)) = MAsymPK (normMsg m)
normMsg m@(MInvKey (MMVar _)) = m
normMsg (MInvKey m)           = normMsg m
normMsg (MHash  m)            = MHash (normMsg m)
normMsg (MTup  m1 m2)         = MTup (normMsg m1) (normMsg m2)
normMsg (MEnc  m1 m2)         = MEnc (normMsg m1) (normMsg m2)
normMsg (MSymK m1 m2)         = MSymK (normMsg m1) (normMsg m2)
normMsg (MShrK m1 m2)
  | m1' < m2' = MShrK m1' m2'
  | otherwise = MShrK m2' m1'
  where
    m1' = normMsg m1
    m2' = normMsg m2
normMsg (MAsymPK m)           = MAsymPK (normMsg m)
normMsg (MAsymSK m)           = MAsymSK (normMsg m)


-- | Splits a message into the list of non-'trivial' messages accessible using
-- projection only.
--
-- Postcondition: All messages in the list are non-'trivial'.
splitNonTrivial :: Message -> [Message]
splitNonTrivial (MTup m1 m2) = splitNonTrivial m1 `mplus` splitNonTrivial m2
splitNonTrivial m            = do
  guard (not $ trivial m)
  return m



------------------------------------------------------------------------------
-- ISAR Output
------------------------------------------------------------------------------

-- | Textual symbolic application of the substitution.
-- TODO: Remove hack about thread identifier to state assignment.
esplSubst :: LocalId -> IsarConf -> Doc -> Doc
esplSubst (LocalId (_,tid)) conf var
  | tid == 0 = isarSubst conf <> parens var
  | otherwise = text "s" <> parens var

instance Isar TID where
  isar _ tid = text "tid" <> int (getTID tid)

instance Isar ArbMsgId where
  isar _ (FreeVarId aid)  = text "a" <> int aid
  isar _ (BoundVarId bid) = text "b" <> int bid

instance Isar LocalId where
  isar conf (LocalId (i, tid)) = isar conf i <-> isar conf tid

instance Isar Fresh where
  isar conf (Fresh i) = text "LN" <-> isar conf i

instance Isar AVar where
  isar conf (AVar i) = esplSubst i conf (text "AV" <-> isar conf i)

instance Isar MVar where
  isar conf (MVar i) = esplSubst i conf (text "MV" <-> isar conf i)

instance Isar Message where
  isar conf x = case x of
      (MConst i)    -> text "LC" <-> isar conf i
      (MFresh i)    -> isar conf i
      (MAVar i)     -> isar conf i
      (MMVar i)     -> isar conf i
      (MArbMsg i)   -> isar conf i
      (MHash m)     -> text "Hash" <-> ppTup m
      pt@(MTup _ _) -> ppTup pt
      (MEnc m k)    -> text "Enc" <-> sep [ppTup m, ppTup k]
      (MSymK a b)   -> text "K" <-> sep [ppTup a, ppTup b]
      (MShrK a b)   -> text "Kbd" <-> sep [ppTup a, ppTup b]
      (MAsymPK a)   -> text "PK" <-> ppTup a
      (MAsymSK a)   -> text "SK" <-> ppTup a
      (MInvKey m)   -> text "inv" <> parens (isar conf m)
    where
      -- pretty print a tuple as right associate list
      ppTup m@(MTup _ _) = nestShort n ldelim rdelim (fsep $ punctuate comma $ map (isar conf) $ split m)
      ppTup m = nestShort' "(" ")" (isar conf m)
      -- split right associate nested tuples
      split (MTup m1 m2) = m1 : split m2
      split m = [m]
      -- determine output parameters
      (n,ldelim,rdelim)
        | isPlainStyle conf = (3, text "{|", text "|}")
        | otherwise        = (2, symbol "\\<lbrace>", symbol "\\<rbrace>")


------------------------------------------------------------------------------
-- SP Theory Output
------------------------------------------------------------------------------

sptTID :: TID -> Doc
sptTID = text . show

sptArbMsgId :: ArbMsgId -> Doc
sptArbMsgId (FreeVarId aid)  = char 'a' <> int aid
sptArbMsgId (BoundVarId bid) = char 'b' <> int bid

sptLocalId :: LocalId -> Doc
sptLocalId (LocalId (i, tid)) = sptId i <> sptTID tid

sptFresh :: Fresh -> Doc
sptFresh = (char '~'  <>) . sptLocalId . getFresh

sptAVar :: AVar -> Doc
sptAVar = sptLocalId . getAVar

sptMVar :: MVar -> Doc
sptMVar = (char '?'  <>) . sptLocalId . getMVar

sptMessage :: Message -> Doc
sptMessage x = case x of
  (MConst i)    -> char '\'' <> sptId i <> char '\''
  (MFresh i)    -> sptFresh i
  (MAVar i)     -> sptAVar i
  (MArbMsg i)    -> sptArbMsgId i
  (MMVar i)     -> sptMVar i
  (MHash m)     -> text "h" <> ppBetween 1 "(" ")" m
  pt@(MTup _ _) -> ppBetween 1 "(" ")" pt
  (MEnc m k)    -> fcat [ppBetween 1 "{" "}" m, sptMessage k]
  (MSymK a b)   -> fcat [text "k(", sptMessage a, comma, sptMessage b, text ")"]
  (MShrK a b)   -> fcat [text "k[", sptMessage a, comma, sptMessage b, text "]"]
  (MAsymPK a)   -> text "pk" <> ppBetween 1 "(" ")" a
  (MAsymSK a)   -> text "sk" <> ppBetween 1 "(" ")" a
  (MInvKey m)   -> text "inv" <> ppBetween 1 "(" ")" m
  where
  -- pretty print a tuple as right associate list
  ppBetween n lead finish m@(MTup _ _) =
    fcat . (text lead :) . (++[text finish]) . map (nest n) . punctuate (text ", ") . map sptMessage $ split m
  ppBetween _ lead finish m = text lead <> sptMessage m <> text finish
  -- split right associate nested tuples
  split (MTup m1 m2) = m1 : split m2
  split m = [m]

