{-# LANGUAGE Unsafe #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE Rank2Types #-}

-- | Helper routines for exposing @IO@ operations on objects with
-- mutable labels.  The mutable labels are implemented by type
-- 'MLabel', and have an immutable meta-label (or \"label label\")
-- protecting the mutable label.
-- It is reasonable to allow untrusted code to modify labels by
-- exporting a type-restricted version of 'modifyMLObjLabelP'.  When
-- this happens, asynchronous exceptions are sent to any other threads
-- inside 'mblessTCB' or 'mblessPTCB' if the new label revokes their
-- access.
module LIO.TCB.MLObj (
  -- * Objects with mutable labels
    MLObj(..), mlObjTCB, mlPolicyObjTCB, modifyMLObjLabelP
  , mblessTCB, mblessPTCB
  -- * Internal details
  -- ** Mutable labels
  , MLabel(..)
  , newMLabelP, labelOfMlabel, readMLabelP, withMLabelP, modifyMLabelP
  , MLabelOf(..)
  -- ** MLabel modificaton policies
  , MLabelPolicyDefault(..), MLabelPolicy(..), InternalML(..), ExternalML(..)
  -- ** Helper class for variadic lifting
  , LabelIO(..)
  ) where

import safe Control.Concurrent
import safe qualified Control.Exception as IO
import safe Control.Monad
import safe Data.Map (Map)
import safe qualified Data.Map as Map
import safe Data.IORef
import safe Data.Typeable
import safe Data.Unique

import safe LIO.Core
import safe LIO.Error
import safe LIO.Label
import LIO.TCB

-- | Class of policies for when it is permissible to update an
-- 'MLabel'.
class MLabelPolicy policy l where
  mlabelPolicy :: (PrivDesc l p) => policy -> p -> l -> l -> LIO l ()

-- | Class for 'MLabelPolicy's that don't encode any interesting
-- values.  This allows 'mlObjTCB' to create an 'MLObj' without
-- requiring a policy argument.
class MLabelPolicyDefault policy where
  mlabelPolicyDefault :: policy

-- | 'InternalML' is for objects contained entirely within Haskell,
-- such as a variable.  Raising the label can't cause data to leak.
data InternalML = InternalML deriving (Show, Typeable)
instance MLabelPolicy InternalML l where
  mlabelPolicy _ p lold lnew =
    unless (canFlowToP p lold lnew) $ labelError "InternalML" [lold, lnew]
instance MLabelPolicyDefault InternalML where
  mlabelPolicyDefault = InternalML

-- | 'ExternalML' is for objects that communicate to the outside
-- world, where extra privileges are required since once data gets
-- out, so as to vouch for the fact that the other end of a socket
-- won't arbitrarily downgrade data.
data ExternalML = ExternalML deriving (Show, Typeable)
instance MLabelPolicy ExternalML l where
  mlabelPolicy _ p lold lnew =
    unless (canFlowToP p lold lnew && canFlowToP p lnew lold) $
    labelError "ExternalML" [lold, lnew]
instance MLabelPolicyDefault ExternalML where
  mlabelPolicyDefault = ExternalML

-- | A mutable label.  Consists of a static label on the label, a
-- mutable label, and a list of threads currently accessing the label.
-- This is intended to be used by privileged code implementing @IO@
-- abstractions with mutable labels.  Routines for accessing such an
-- @IO@ abstraction should perform tne @IO@ from within a call to
-- 'withMLabelP', to ensure an exception is raised if another thread
-- revokes access with 'modifyMLabelP'.
data MLabel policy l = MLabelTCB {
    mlLabelLabel :: !l
  , mlLabel :: !(IORef l)
  , mlUsers :: !(MVar (Map Unique (l -> IO Bool)))
  , mlPolicy :: policy
  } deriving (Typeable)

-- | Returns the immutable label that controls access to the mutable
-- label value of an 'MLabel'.
labelOfMlabel :: MLabel policy l -> l
labelOfMlabel (MLabelTCB ll _ _ _) = ll

-- | Retreive a snapshot of the value of a mutable label.  Of course,
-- it may already have changed by the time you process it.
readMLabelP :: (PrivDesc l p) => Priv p -> MLabel policy l -> LIO l l
readMLabelP p (MLabelTCB ll r _ _) = do
  taintP p ll
  ioTCB $ readIORef r

-- | Run an action that should be protected by a mutable label.  An
-- exception is thrown if the invoking thread cannot write to the
-- mutable label given the privileges.  No attempt is made to adjust
-- the current label, even if doing so would make the permissions
-- acceptable.
-- Note that if the label changes after this function has been
-- invoked, an exception may be raised in the middle of the protected
-- action.
withMLabelP :: (PrivDesc l p) =>
               Priv p -> MLabel policy l -> LIO l a -> LIO l a
withMLabelP p (MLabelTCB ll r mv _) action = LIOTCB $ \s -> do
  let run (LIOTCB io) = io s
  run $ taintP p ll
  tid <- myThreadId
  u <- newUnique
  let check lnew = do
        LIOState { lioLabel = lcur, lioClearance = ccur } <- readIORef s
        if canFlowToP p lcur lnew && canFlowToP p lnew lcur
          then return True
          else do IO.throwTo tid LabelError {
                      lerrContext = []
                    , lerrFailure = "withMLabelP label changed"
                    , lerrCurLabel = lcur
                    , lerrCurClearance = ccur
                    , lerrPrivs = [GenericPrivDesc $ privDesc p]
                    , lerrLabels = [lnew]
                  return False
      enter = modifyMVar_ mv $ \m -> do
        void $ readIORef r >>= check
        return $ Map.insert u check m
      exit = modifyMVar_ mv $ return . Map.delete u
  IO.bracket_ enter exit $ run action

-- | Change the mutable label in an 'MLabel'.  Raises asynchronous
-- exceptions in other threads that are inside 'withMLabelP' if the
-- new label revokes their access.
modifyMLabelP :: (PrivDesc l p, MLabelPolicy policy l) =>
                 Priv p -> MLabel policy l -> (l -> LIO l l) -> LIO l ()
modifyMLabelP p (MLabelTCB ll r mv pl) fn = withContext "modifyMLabelP" $ do
  guardWriteP p ll
  s <- LIOTCB return
  let run (LIOTCB io) = io s
  ioTCB $ modifyMVar_ mv $ \m -> do
    lold <- readIORef r
    lnew <- run $ fn lold
    () <- run $ mlabelPolicy pl p lold lnew
    writeIORef r lnew
    Map.fromList `fmap` filterM (($ lnew) . snd) (Map.assocs m)

-- | @newMLabelP policy ll l@ creates an 'MLabel'.  @policy@ is a
-- policy specifying under what conditions it is permissible to change
-- the label.  @ll@ is the immutable label of the mutable label.  @l@
-- is the initial value of this mutable label.
newMLabelTCB :: policy -> l -> l -> LIO l (MLabel policy l)
newMLabelTCB policy ll l = do
  r <- ioTCB $ newIORef l
  mv <- ioTCB $ newMVar Map.empty
  return $ MLabelTCB ll r mv policy

-- | Create an 'MLabel', performing access control checks to ensure
-- that the labels are within the range allowed given the current
-- label and clearance, and the supplied privileges.
newMLabelP :: (PrivDesc l p) =>
              Priv p -> policy -> l -> l -> LIO l (MLabel policy l)
newMLabelP p policy ll l = do
  guardAllocP p ll
  guardAllocP p l
  newMLabelTCB policy ll l

-- | Class of objects with mutable labels.
class MLabelOf t where
  mLabelOf :: t policy l a -> MLabel policy l

-- | IO Object with a mutable label.  By contrast with
-- 'LIO.TCB.LObj.LObj', the label on an 'MLObj' can change over time.
-- If this happens, the internal 'MLabel' ensures that threads
-- accessing the object receive an asynchronous exception.
data MLObj policy l object = MLObjTCB !(MLabel policy l) !object
                             deriving (Typeable)

instance MLabelOf MLObj where
  mLabelOf (MLObjTCB ml _) = ml

-- | Like 'mlObjTCB', but create an 'MLObj' with a particular policy
-- value.  Note that you don't need to use this for 'ExternalML' and
-- 'InternalML', as these don't have anything interesting in the
-- policy value, only the type matters.  This might be useful if, for
-- instance, you wished to design a new policy type that embeds a
-- clearance.
mlPolicyObjTCB :: policy -> l -> l -> a -> LIO l (MLObj policy l a)
mlPolicyObjTCB policy ll l a = do
  ml <- newMLabelTCB policy ll l
  return $ MLObjTCB ml a

-- | @'mlObjTCB' ll l a@ creates an 'MLObj' wrapping some @IO@ object
-- @a@.  Here @ll@ is the label on the label, which remains immutable
-- over the lifetime of the 'MLObj'.  @l@ is the initial value of the
-- mutable lable.
mlObjTCB :: (MLabelPolicyDefault policy) =>
            l -> l -> a -> LIO l (MLObj policy l a)
mlObjTCB ll l a = do
  ml <- newMLabelTCB mlabelPolicyDefault ll l
  return $ MLObjTCB ml a

-- | Modify the 'MLabel' within an 'MLObj'.
modifyMLObjLabelP :: (PrivDesc l p, MLabelPolicy policy l) =>
                     Priv p -> MLObj policy l a -> (l -> LIO l l) -> LIO l ()
modifyMLObjLabelP p (MLObjTCB ml _) = modifyMLabelP p ml

#include "TypeVals.hs"

-- | Takes a @'liftIO'@-like function and an @IO@ function of an
-- arbitrary number of arguments (up to 10).  Applies the arguments to
-- the @IO@ function, then passed the result to its argument funciton
-- to transform it into an @LIO@ function.
class LabelIO l io lio | l io -> lio where
  labelIO :: (forall r. IO r -> LIO l r) -> io -> lio
instance LabelIO l (IO r) (LIO l r) where
  {-# INLINE labelIO #-}
  labelIO f = f
#define WRAPIO(types, vals) \
instance LabelIO l (types -> IO r) (types -> LIO l r) where { \
  {-# INLINE labelIO #-}; \
  labelIO f io vals = f $ io vals; \
TypesVals (WRAPIO)

-- | The 'MLObj' equivalent of 'blessTCB' in
-- "LIO.TCB.LObj#v:blessTCB".  Use this for conveniently providing
-- @LIO@ versions of standard @IO@ functions.
mblessTCB :: (LabelIO l io lio, Label l) =>
             String -> (a -> io) -> MLObj policy l a -> lio
{-# INLINE mblessTCB #-}
mblessTCB name io = mblessPTCB name io noPrivs

-- | The 'MLObj' equivalent of 'blessPTCB' in
-- "LIO.TCB.LObj#v:blessPTCB".  Use this for conveniently providing
-- @LIO@ versions of standard @IO@ functions.
mblessPTCB :: (LabelIO l io lio, Label l, PrivDesc l p) =>
              String -> (a -> io) -> Priv p -> MLObj policy l a -> lio
{-# INLINE mblessPTCB #-}
mblessPTCB name io p (MLObjTCB ml a) = labelIO check (io a)
  where check ioa = withContext name $ withMLabelP p ml $ ioTCB ioa