{-# LINE 1 "src/OpenSSL/EVP/Digest/Algorithm.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface #-}

{- |
   Maintainer:  simons@cryp.to
   Stability:   provisional
   Portability: portable

   Low-level bindings to OpenSSL's EVP interface. Most users do not need this
   code. Check out "OpenSSL.Digest" for a more comfortable interface.
-}

module OpenSSL.EVP.Digest.Algorithm where

import OpenSSL.EVP.Digest.Initialization
import OpenSSL.EVP.Digest.Error ( UnknownAlgorithm(..) )

import Control.Exception
import Data.Maybe
import Data.String ( IsString(..) )
import Foreign
import Foreign.C
import System.IO.Unsafe as IO




-- | An opaque handle into OpenSSL's collection of message digest algorithms.
-- Use 'digestByName' to look up any of the available algorithms by name. For
-- the sake of convenience, 'Algorithm' is an instance of 'IsString' so
-- that the compiler can transparently map 'String' literals to algorithms via
-- 'fromString' if the @XOverloadedStrings@ extension is enabled.
--
-- >>> fromString "sha256" == digestByName "sha256"
-- True

newtype Algorithm = Algorithm (Ptr ())
  deriving (Show, Eq)

instance IsString Algorithm where
  fromString = digestByName

-- | Look up a digest algorithm engine by name. Algorithms usually offered by
-- OpenSSL are "md2", "md5", "sha1", "mdc2", "ripemd160", "blake2b512",
-- "blake2s256", "sha224", "sha256", "sha384", and "sha512", but the exact set
-- may vary between platforms. Throws 'UnknownAlgorithm' if the requested
-- algorithm is not known.

digestByName :: String -> Algorithm
digestByName algo =
  fromMaybe (throw (UnknownAlgorithm algo)) (digestByName' algo)

-- | Variant of 'digestByName' that signals failure by evaluating to 'Nothing'
-- rather than failing.
--
-- >>> digestByName' "sha256" == Just (digestByName "sha256")
-- True
-- >>> digestByName' "Guess what?" :: Maybe Algorithm
-- Nothing

digestByName' :: String -> Maybe Algorithm
digestByName' algo = do
  let Algorithm p = IO.unsafePerformIO $ do
                      initializeEVPDigests
                      withCString algo (return . _digestByName)
  if p == nullPtr then Nothing else Just (Algorithm p)

-- | Return the size of the digest in bytes that the given algorithm will produce.
--
-- >>> digestSize (digestByName "sha256")
-- 32

digestSize :: Algorithm -> Int
digestSize = fromIntegral . _digestSize

-- | The largest possible digest size of any of the algorithms supported by
-- this library will generate. So if you want to store a digest without
-- bothering to retrieve the appropriate size with 'digestSize' first, allocate
-- a buffer of that size.

maxDigestSize :: Int
maxDigestSize = 64
{-# LINE 82 "src/OpenSSL/EVP/Digest/Algorithm.hsc" #-}

-- | Return the block size the the given algorithm operates with.
--
-- >>> digestBlockSize (digestByName "sha256")
-- 64

digestBlockSize :: Algorithm -> Int
digestBlockSize = fromIntegral . _digestBlockSize

-------------------------------------------------------------------------------

foreign import ccall unsafe "openssl/evp.h EVP_get_digestbyname"
  _digestByName :: CString -> Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_MD_size"
  _digestSize :: Algorithm -> CInt

foreign import ccall unsafe "openssl/evp.h EVP_MD_block_size"
  _digestBlockSize :: Algorithm -> CInt