{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >= 710
{-# LANGUAGE StaticPointers #-}
#endif
module Hackage.Security.Trusted (
    module Hackage.Security.Trusted.TCB
    -- * Derived functions
  , (<$$>)
    -- ** Role verification
  , VerifyRole(..)
    -- ** File info verification
  , verifyFileInfo
  , trustedFileInfoEqual
  ) where

import Data.Function (on)
import Data.Time
import Hackage.Security.TUF
import Hackage.Security.Trusted.TCB hiding (DeclareTrusted)
import Hackage.Security.Util.IO
import Hackage.Security.Util.Path

{-------------------------------------------------------------------------------
  Combinators on trusted values
-------------------------------------------------------------------------------}

-- | Apply a static function to a trusted argument
(<$$>) :: StaticPtr (a -> b) -> Trusted a -> Trusted b
(<$$>) = trustApply . trustStatic

{-------------------------------------------------------------------------------
  Role verification
-------------------------------------------------------------------------------}

class VerifyRole a where
  verifyRole :: Trusted Root      -- ^ Root data
             -> TargetPath        -- ^ Source (for error messages)
             -> Maybe FileVersion -- ^ Previous version (if available)
             -> Maybe UTCTime     -- ^ Time now (if checking expiry)
             -> Signed a          -- ^ Mirrors to verify
             -> Either VerificationError (SignaturesVerified a)

instance VerifyRole Root where
  verifyRole = verifyRole' . (static (rootRolesRoot . rootRoles) <$$>)

instance VerifyRole Timestamp where
  verifyRole = verifyRole' . (static (rootRolesTimestamp . rootRoles) <$$>)

instance VerifyRole Snapshot where
  verifyRole = verifyRole' . (static (rootRolesSnapshot . rootRoles) <$$>)

instance VerifyRole Mirrors where
  verifyRole = verifyRole' . (static (rootRolesMirrors . rootRoles) <$$>)

{-------------------------------------------------------------------------------
  File info verification
-------------------------------------------------------------------------------}

-- | Verify 'FileInfo'
--
-- We compare file lengths before computing hashes, but once we have verified
-- that the file lengths match we compute _all_ hashes, and then compare the
-- resulting sets. This is the right thing to do: sure, in the case where the
-- file info does _not_ match this is a waste of effort. However, we expect
-- that in the majority of cases the file info _will_ match, in which case
-- having to traverse the file multiple times to compute each hash, rather than
-- traversing the file once and computing all hashes at once, is inefficient.
--
-- (Of course, right now the difference is moot since we only use one hash.)
verifyFileInfo :: forall root. IsFileSystemRoot root
               => Path (Rooted root) -> Trusted FileInfo -> IO Bool
verifyFileInfo fp trustedInfo = lazyAndM [
      verifyFileLength
    , (knownFileInfoEqual info) <$> computeFileInfo fp
    ]
  where
    verifyFileLength :: IO Bool
    verifyFileLength = (== fileInfoLength) <$> getFileLength

    getFileLength :: IO FileLength
    getFileLength = FileLength . fromInteger <$> getFileSize fp

    info@FileInfo{..} = trusted trustedInfo

-- | Variation on 'knownFileInfoEqual' for 'Trusted' 'FileInfo'
trustedFileInfoEqual :: Trusted FileInfo -> Trusted FileInfo -> Bool
trustedFileInfoEqual = knownFileInfoEqual `on` trusted

{-------------------------------------------------------------------------------
  Auxiliary
-------------------------------------------------------------------------------}

lazyAndM :: Monad m => [m Bool] -> m Bool
lazyAndM []     = return True
lazyAndM (m:ms) = do b <- m
                     case b of
                       False -> return False
                       True  -> lazyAndM ms