{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}

-- | Provide security for computations involving side-effects. 
module SecLib.SecIO where

import SecLib.Lattice
import SecLib.Sec 

import Data.IORef
import Network.Socket

-- Monad

{-| This type represents secure side-effect computations. 
    These computations perform side-effects at security level, at least, @s@ and return
    a value of type @a@ with confidentiality level, at least, @s@.
newtype SecIO s a = MkSecIO (IO (Sec s a))

instance Functor (SecIO s) where
  h `fmap` (MkSecIO io) = MkSecIO ( do sec <- io
                                       return (h `fmap` sec) )

instance Monad (SecIO s) where
  return x =
    MkSecIO (return (return x))
  MkSecIO m >>= k =
    MkSecIO (do sa <- m
                let MkSecIO m' = k (reveal sa)

-- SecIO functions

-- | Lift a pure confidential value into a secure side-effect computation. 
value :: Sec s a -> SecIO s a
value sa = MkSecIO (return sa)

-- | Execute secure computations.
run :: SecIO s a -> IO (Sec s a)
run (MkSecIO m) = m

-- | Safely downgrade the security level indicating the side-effects performed by the computation. 
plug :: Less sl sh => SecIO sh a -> SecIO sl (Sec sh a)
plug secio_sh@(MkSecIO m) = less sl sh `seq` secio_sl
                                 (secio_sl) = MkSecIO (do sha <- m
                                                          return (sec sha))
                                 sl = unSecIOType secio_sl
                                 sh = unSecIOType secio_sh

-- | Internal function, not exported. For type-checking purposes.
unSecIOType :: SecIO s a -> s   
unSecIOType _ = undefined

-- | Break the abstraction provided by 'SecIO'. It is used only in trustworthy code!
revealIO :: SecIO s a -> IO (Sec s a)
revealIO (MkSecIO io) = io

    Represent secure locations.
    This data type is internally used to easily introduce new side-effects into this module.
    Type @t@ is the raw type needed to perform side-effects. For instance, 
    @t@ is 'FilePath' for files and @'IORef' a@ for references. Type @s@ is the confidentiality level of the 
    location. Type @a@ is the type of values written and read form @t@. 
data Loc t s a b = MkLoc t

{-| Introduce the underlining operations for reading and writing values of type @a@ provided 
    the type @t@.
class UnsecureRW t a b | t -> a b where
   unsec_write :: t -> a -> IO b 
   unsec_read  :: t -> IO a       

{-| Introduce safe read and write operations for locations of type @'Loc' t s a@.
    This class acts as a wrapper for the type class @'UnsecureRW'@.
class UnsecureRW t a b => SecureRW t s a b where
   {-| It provides a secure read operation when reading values might be visible 
       for the attacker (from inside of the program). 
       For example, reading from a channel in a network communication changes the 
       buffer pointer for that channel, which can be exploited to leak information. 
   effectful_read  :: Loc t s a b -> SecIO s a
   {-| It provides a secure read operation when reading values is not observable by the attacker. 
       For example, reading from references does not produce any observable effect.
   effectless_read :: Loc t s a b -> SecIO s' (Sec s a)
   -- | It provides a secure write operation.
   effectful_write :: Loc t s a b -> a -> SecIO s b

instance UnsecureRW t a b => SecureRW t s a b where
    effectless_read (MkLoc loc)   = MkSecIO ( (sec.sec) `fmap` unsec_read loc)
    effectful_read  (MkLoc loc)   = MkSecIO (sec `fmap` (unsec_read loc))
    effectful_write (MkLoc loc) a = MkSecIO (sec `fmap` (unsec_write loc a))

-- | Locations that represent files   
type File s = Loc FilePath s String ()

instance UnsecureRW FilePath String () where
   unsec_read file      = readFile file
   unsec_write file str = writeFile file str

-- | Secure read operation for files.
readFileSecIO  :: File s -> SecIO s' (Sec s String)
readFileSecIO  = effectless_read

-- | Secure write operation for files. 
writeFileSecIO :: File s -> String -> SecIO s ()
writeFileSecIO = effectful_write
-- Creation of files: we do not allow that!

-- | Locations that represent references. 
type Ref s a = Loc (IORef a) s a ()

instance UnsecureRW (IORef a) a () where
   unsec_read  ref    = readIORef ref
   unsec_write ref a  = writeIORef ref a

{-| Creation of files is only allowed by trustworhty code. 
Observe that by creating a file, the 
confidentiality level of its content is being established.
Therefore, the attacker can create a public file using the name 
of a secret file and just read it! |-} 
mkFile :: FilePath -> File s 
mkFile f =  MkLoc f

-- | Secure read operation for references.
readRefSecIO  :: Ref s a -> SecIO s' (Sec s a)
readRefSecIO  = effectless_read 

-- | Secure write operation for references.
writeRefSecIO :: Ref s a -> a -> SecIO s ()
writeRefSecIO = effectful_write

{-| Secure creation of a reference. We assume that the attacker 
    has no manner to observe the side-effect of creating a reference from inside of the program, 
    for example, by inspecting the amount of free memory. At the moment, there are no such functions inside of 
    the monad 'SecIO'. Nevertheless, if there is a consideration of include, for instance, a function that returns 
    the amount of free memory in the program, then the function type of 'newIORefSecIO' needs 
    to be changed. 
newIORefSecIO :: s -> a -> SecIO s' (Ref s a) 
newIORefSecIO _ a = MkSecIO ( (sec.MkLoc) `fmap` newIORef a )

{-|  Location that represents the screen-keyword. Here, we can choose between 
     a) The attacker is on the screen-keyword, which implies that when taking input 
     from the screen has an effect -- the attacker can see when the input is required. 
     Therefore, we need to implement taking input from the keyword using 'effectful_read'.
     b) The attacker is not on the screen-keyword. In this case, we implement 
     reading using 'effectless_read'. We choose option a) as a model.
type Screen s = Attacker s => Loc () s String ()

{-| It sets the security level of the standard input/output 
(usually the keyboard/screen).
To be used only by trustworthy code. |-} 
mkScreen :: () -> Screen s  
mkScreen _ =  MkLoc ()

instance UnsecureRW () String () where
   unsec_read  _    = getLine 
   unsec_write _ a  = putStr a

-- | Secure input from the keyword.
getLineSecIO  :: Attacker s => Screen s -> SecIO s String
getLineSecIO scr = observe s `seq` m 
                   where m = effectful_read scr 
                         s = unSecIOType m

-- | Secure output to the screen.
putStrSecIO :: Attacker s => Screen s -> String -> SecIO s ()
putStrSecIO scr str = observe s `seq` m
                      where m = effectful_write scr str
                            s = unSecIOType m

-- | Secure output to the screen.
putStrLnSecIO :: Attacker s => Screen s -> String -> SecIO s ()
putStrLnSecIO scr str = putStrSecIO scr (str++"\n")

-- | It is a derived operation for reading from files (legacy code).
s_read :: Less s' s => File s' -> SecIO s String
s_read file =
   do ss <- readFileSecIO file
      value (up ss)

-- | It is a derived operation for writing into files (legacy code).
s_write :: Less s s' => File s' -> String -> SecIO s (Sec s' ())
s_write file s = plug (writeFileSecIO file s)

 --- Network operations

-- | Locations that represent network communications via sockets. 
type SecSocket s   = Loc (Socket,Int) s String Int  

-- | It defines a socket address at confidentiality level @s@.
data SecSockAddr s = MkSockAddr SockAddr

-- | Wrapper for 'inet_addr'.
inet_addrSecIO :: String -> SecIO s HostAddress
inet_addrSecIO s = MkSecIO $ (return `fmap` (inet_addr s))

-- | Assing a port to the 'AF_INET' sockets. Port numbers are public. 
portInet :: PortNumber -> SecSockAddr s -> SecSockAddr s 
portInet p (MkSockAddr (SockAddrInet port host)) = (MkSockAddr (SockAddrInet p host)) 

{- | Create a socket at confidentiality level @s@. The creation has no visible side-effects, e.g. 
     sockets cannot be compared. This function is essentially a wrapper for 'socket'.
socketSecIO :: Family -> SocketType -> ProtocolNumber -> 
               SecIO s' (SecSocket s)
socketSecIO f s p =  MkSecIO ( (sec.(\s -> MkLoc (s,0))) `fmap` socket f s p)

-- Question: it might be that it is possible to return 
-- SecIO s' () if it is the case that sIsBoundSecIO is the 
-- only observer if a socket is bind or not.

{- Bind a socket to a given address. It is considered an observable operation since the attacker 
   can observe if the 
   socket is bound by calling 'sIsBoundSecIO'. 
bindSocketSecIO :: SecSocket s -> SecSockAddr s -> SecIO s ()
bindSocketSecIO (MkLoc (s,_)) (MkSockAddr address) = 
                 MkSecIO (sec `fmap` bindSocket s address) 

-- | Return if a socket is bound to some address.
sIsBoundSecIO :: SecSocket s -> SecIO s' (Sec s Bool)
sIsBoundSecIO (MkLoc (s,_)) = MkSecIO (sec `fmap` (sec `fmap` sIsBound s)) 

-- | Wrapper for 'accept'.  
acceptSecIO :: SecSocket s -> SecIO s (SecSocket s, SecSockAddr s)
acceptSecIO (MkLoc (s,_)) = MkSecIO (sec `fmap` m)
                            where m = do (sock, addr) <- accept s   
                                         return (MkLoc (sock,0), MkSockAddr addr)

instance UnsecureRW (Socket,Int) String Int where
  unsec_read  (sock,n)     = recv sock n  
  unsec_write (sock,_) str = send sock str   

-- | Secure read operation for sockets. 
recvSecIO :: SecSocket s -> Int -> SecIO s String
recvSecIO (MkLoc (s,_)) n   = effectful_read (MkLoc (s,n))

-- | Secure write operation for sockets. 
sendSecIO :: SecSocket s -> String -> SecIO s Int
sendSecIO = effectful_write 

-- | Connects to a socket. It is a wrapper for 'connect'.
connectSecIO :: SecSocket s -> SecSockAddr s -> SecIO s ()
connectSecIO (MkLoc (s,_)) (MkSockAddr sa) = MkSecIO (sec `fmap` connect s sa) 

-- | Determines a given socket is connected. It is a wrapper for 'sIsConnected'.
sIsConnectedSecIO :: SecSocket s -> SecIO s' (Sec s Bool)
sIsConnectedSecIO (MkLoc (s,_)) = MkSecIO (sec `fmap` (sec `fmap` sIsConnected s))