botan-low-0.1.0.0: src/Botan/Low/Error/Internal.hs
{-|
Module : Botan.Low.Error
Description : Error codes and exception handling
Copyright : (c) 2023-2024, Apotheca Labs
(c) 2024-2025, Haskell Foundation
License : BSD-3-Clause
Maintainer : joris@well-typed.com, leo@apotheca.io
Stability : experimental
Portability : POSIX
-}
{-# LANGUAGE DerivingVia #-}
module Botan.Low.Error.Internal (
-- * Error codes
BotanErrorCode
, botanErrorDescription
, BotanErrorMessage(..)
, botanErrorLastExceptionMessage
-- * Exception hierarchy
, SomeBotanException(..)
, toBotanException
, fromBotanException
-- ** Individual exceptions
, InvalidInputException(..)
, BadMACException(..)
, InsufficientBufferSpaceException(..)
, StringConversionException(..)
, ExceptionThrownException(..)
, OutOfMemoryException(..)
, SystemErrorException(..)
, InternalErrorException(..)
, BadFlagException(..)
, NullPointerException(..)
, BadParameterException(..)
, KeyNotSetException(..)
, InvalidKeyLengthException(..)
, InvalidObjectStateException(..)
, NotImplementedException(..)
, InvalidObjectException(..)
, UnknownException(..)
-- ** Throwing and catching
, throwBotanError
, throwBotanIfNegative
, throwBotanIfNegative_
, throwBotanCatchingInvalidVerifier
, throwBotanCatchingInvalidInput
, throwBotanCatchingBool
, throwBotanCatching
, throwBotanErrorWithCallstack
) where
import Botan.Bindings.ConstPtr (ConstPtr (..))
import Botan.Bindings.Error
import Botan.Low.Internal.ByteString
import Control.Exception
import Control.Monad
import Data.ByteString (ByteString)
import Data.Typeable
import Foreign.C.Types
import GHC.Stack
import Prelude
{-------------------------------------------------------------------------------
Errors
-------------------------------------------------------------------------------}
newtype BotanErrorCode = BotanErrorCode CInt
deriving stock (Show, Eq)
-- | Convert an error code into a string. Returns "Unknown error" if the error
-- code is not a known one.
botanErrorDescription :: BotanErrorCode -> IO ByteString
botanErrorDescription (BotanErrorCode e) = do
descPtr <- botan_error_description e
peekCString descPtr.unConstPtr
newtype BotanErrorMessage = BotanErrorMessage ByteString
deriving newtype Show
-- | Returns a static string stored in a thread local variable which contains
-- the last exception message thrown. WARNING: This string buffer is
-- overwritten on the next call to the FFI layer
botanErrorLastExceptionMessage :: IO BotanErrorMessage
botanErrorLastExceptionMessage = do
msgPtr <- botan_error_last_exception_message
BotanErrorMessage <$> peekCString msgPtr.unConstPtr
{-------------------------------------------------------------------------------
Exception hierarchy
-------------------------------------------------------------------------------}
-- | The SomeBotanException type is the root of the botan exception type
-- hierarchy.
data SomeBotanException = forall e . Exception e => SomeBotanException e
instance Show SomeBotanException where
show (SomeBotanException e) = show e
instance Exception SomeBotanException
toBotanException :: Exception e => e -> SomeException
toBotanException = toException . SomeBotanException
fromBotanException :: Exception e => SomeException -> Maybe e
fromBotanException x = do
SomeBotanException a <- fromException x
cast a
newtype ViaSomeBotanException e = ViaSomeBotanException e
deriving stock Show
instance (Show e, Typeable e) => Exception (ViaSomeBotanException e) where
toException = toBotanException
fromException = fromBotanException
{-------------------------------------------------------------------------------
Individual exceptions
-------------------------------------------------------------------------------}
data InvalidVerifierException
= InvalidVerifierException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException InvalidVerifierException
data InvalidInputException
= InvalidInputException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException InvalidInputException
data BadMACException
= BadMACException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException BadMACException
data InsufficientBufferSpaceException
= InsufficientBufferSpaceException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException InsufficientBufferSpaceException
data StringConversionException
= StringConversionException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException StringConversionException
data ExceptionThrownException
= ExceptionThrownException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException ExceptionThrownException
data OutOfMemoryException
= OutOfMemoryException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException OutOfMemoryException
data SystemErrorException
= SystemErrorException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException SystemErrorException
data InternalErrorException
= InternalErrorException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException InternalErrorException
data BadFlagException
= BadFlagException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException BadFlagException
data NullPointerException
= NullPointerException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException NullPointerException
data BadParameterException
= BadParameterException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException BadParameterException
data KeyNotSetException
= KeyNotSetException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException KeyNotSetException
data InvalidKeyLengthException
= InvalidKeyLengthException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException InvalidKeyLengthException
data InvalidObjectStateException
= InvalidObjectStateException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException InvalidObjectStateException
data NotImplementedException
= NotImplementedException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException NotImplementedException
data InvalidObjectException
= InvalidObjectException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException InvalidObjectException
data UnknownException
= UnknownException BotanErrorCode BotanErrorMessage CallStack
deriving stock (Show)
deriving Exception via ViaSomeBotanException UnknownException
{-------------------------------------------------------------------------------
Exceptions: throwing and catching
-------------------------------------------------------------------------------}
throwBotanError :: HasCallStack => CInt -> IO a
throwBotanError r = throwBotanErrorWithCallstack r callStack
throwBotanIfNegative :: HasCallStack => IO CInt -> IO CInt
throwBotanIfNegative act = do
e <- act
when (e < 0) $ throwBotanErrorWithCallstack e callStack
return e
throwBotanIfNegative_ :: HasCallStack => IO CInt -> IO ()
throwBotanIfNegative_ act = do
e <- act
when (e < 0) $ throwBotanErrorWithCallstack e callStack
-- | 'throwBotanCatching' with @retCode = 'BOTAN_FFI_INVALID_VERIFIER'@.
throwBotanCatchingInvalidVerifier :: HasCallStack => IO CInt -> IO Bool
throwBotanCatchingInvalidVerifier = throwBotanCatching BOTAN_FFI_INVALID_VERIFIER
-- | 'throwBotanCatching' with @retCode = 'BOTAN_FFI_ERROR_INVALID_INPUT'@.
throwBotanCatchingInvalidInput :: HasCallStack => IO CInt -> IO Bool
throwBotanCatchingInvalidInput = throwBotanCatching BOTAN_FFI_ERROR_INVALID_INPUT
-- | @'throwBotanCatching' retCode action@ runs the @action@ and catches return
-- code 0 (Success) as True and @retCode@ as False. All other return codes are
-- thrown as exceptions.
throwBotanCatching :: HasCallStack => CInt -> IO CInt -> IO Bool
throwBotanCatching x act = do
result <- act
case result of
BOTAN_FFI_SUCCESS -> return True
_ | x == result -> return False
| otherwise -> throwBotanErrorWithCallstack result callStack
-- NOTE: Catches 1 as True and 0 as False, throws all other values
throwBotanCatchingBool :: HasCallStack => IO CInt -> IO Bool
throwBotanCatchingBool act = do
result <- act
case result of
0 -> return False
1 -> return True
_ -> throwBotanErrorWithCallstack result callStack
throwBotanErrorWithCallstack :: CInt -> CallStack -> IO a
throwBotanErrorWithCallstack e cs = do
emsg <- botanErrorLastExceptionMessage
case e of
-- Note: we do not include a case for @BOTAN_FFI_SUCCESS@ since it
-- signals that the function call was successful.
BOTAN_FFI_INVALID_VERIFIER -> throwIO $ InvalidVerifierException bec emsg cs
BOTAN_FFI_ERROR_INVALID_INPUT -> throwIO $ InvalidInputException bec emsg cs
BOTAN_FFI_ERROR_BAD_MAC -> throwIO $ BadMACException bec emsg cs
BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE -> throwIO $ InsufficientBufferSpaceException bec emsg cs
BOTAN_FFI_ERROR_STRING_CONVERSION_ERROR -> throwIO $ StringConversionException bec emsg cs
BOTAN_FFI_ERROR_EXCEPTION_THROWN -> throwIO $ ExceptionThrownException bec emsg cs
BOTAN_FFI_ERROR_OUT_OF_MEMORY -> throwIO $ OutOfMemoryException bec emsg cs
BOTAN_FFI_ERROR_SYSTEM_ERROR -> throwIO $ SystemErrorException bec emsg cs
BOTAN_FFI_ERROR_INTERNAL_ERROR -> throwIO $ InternalErrorException bec emsg cs
BOTAN_FFI_ERROR_BAD_FLAG -> throwIO $ BadFlagException bec emsg cs
BOTAN_FFI_ERROR_NULL_POINTER -> throwIO $ NullPointerException bec emsg cs
BOTAN_FFI_ERROR_BAD_PARAMETER -> throwIO $ BadParameterException bec emsg cs
BOTAN_FFI_ERROR_KEY_NOT_SET -> throwIO $ KeyNotSetException bec emsg cs
BOTAN_FFI_ERROR_INVALID_KEY_LENGTH -> throwIO $ InvalidKeyLengthException bec emsg cs
BOTAN_FFI_ERROR_INVALID_OBJECT_STATE -> throwIO $ InvalidObjectStateException bec emsg cs
BOTAN_FFI_ERROR_NOT_IMPLEMENTED -> throwIO $ NotImplementedException bec emsg cs
BOTAN_FFI_ERROR_INVALID_OBJECT -> throwIO $ InvalidObjectException bec emsg cs
_ -> throwIO $ UnknownException bec emsg cs
where
bec = BotanErrorCode e