    -- |
    -- Module      : Crypto.KDF.Argon2
    -- License     : BSD-style
    -- Maintainer  : Vincent Hanquez <vincent@snarc.org>
    -- Stability   : experimental
    -- Portability : unknown
    --
    -- Argon2 hashing function (P-H-C winner)
    --
    -- Recommended to use this module qualified
    --
    -- File started from Argon2.hs, from Oliver Charles
    -- at https://github.com/ocharles/argon2
    --
    module Crypto.KDF.Argon2
        (
          Options(..)
        , TimeCost
        , MemoryCost
        , Parallelism
        , Variant(..)
        , Version(..)
        , defaultOptions
        -- * Hashing function
        , hash
        ) where

    import           Crypto.Internal.ByteArray (ScrubbedBytes, ByteArray, ByteArrayAccess)
    import qualified Crypto.Internal.ByteArray as B
    import           Crypto.Error
    import           Control.Monad (when)
    import           Data.Word
    import           Foreign.C
    import           Foreign.Ptr

    -- | Which variant of Argon2 to use. You should choose the variant that is most
    -- applicable to your intention to hash inputs.
    data Variant =
          Argon2d  -- ^ Argon2i uses data-independent memory access, which is preferred
                   -- for password hashing and password-based key derivation. Argon2i
                   -- is slower as it makes more passes over the memory to protect from
                   -- tradeoff attacks.
        | Argon2i -- ^ Argon2d is faster and uses data-depending memory access, which
                  -- makes it suitable for cryptocurrencies and applications with no
                  -- threats from side-channel timing attacks.
        | Argon2id -- ^ Argon2id is a hybrid of Argon2i and Argon2d, using a combination
                   -- of data-depending and data-independent memory accesses, which gives
                   -- some of Argon2i's resistance to side-channel cache timing attacks
                   -- and much of Argon2d's resistance to GPU cracking attacks
        deriving (Eq,Ord,Read,Show,Enum,Bounded)

    -- | Which version of Argon2 to use
    data Version = Version10 | Version13
        deriving (Eq,Ord,Read,Show,Enum,Bounded)

    -- | The time cost, which defines the amount of computation realized and therefore the execution time, given in number of iterations.
    --
    -- 'FFI.ARGON2_MIN_TIME' <= 'hashIterations' <= 'FFI.ARGON2_MAX_TIME'
    type TimeCost = Word32

    -- | The memory cost, which defines the memory usage, given in kibibytes.
    --
    -- max 'FFI.ARGON2_MIN_MEMORY' (8 * 'hashParallelism') <= 'hashMemory' <= 'FFI.ARGON2_MAX_MEMORY'
    type MemoryCost = Word32

    -- \ A parallelism degree, which defines the number of parallel threads.
    --
    -- 'FFI.ARGON2_MIN_LANES' <= 'hashParallelism' <= 'FFI.ARGON2_MAX_LANES' && 'FFI.ARGON_MIN_THREADS' <= 'hashParallelism' <= 'FFI.ARGON2_MAX_THREADS'
    type Parallelism = Word32

    -- | Parameters that can be adjusted to change the runtime performance of the
    -- hashing.
    data Options = Options
        { iterations  :: !TimeCost
        , memory      :: !MemoryCost
        , parallelism :: !Parallelism
        , variant     :: !Variant     -- ^ Which variant of Argon2 to use.
        , version     :: !Version     -- ^ Which version of Argon2 to use.
        }
        deriving (Eq,Ord,Read,Show)

    saltMinLength :: Int
    saltMinLength = 8

    outputMinLength :: Int
    outputMinLength = 4

    -- specification allows up to 2^32-1 but this is too big for a signed Int
    -- on a 32-bit architecture, so we limit tag length to 2^31-1 bytes
    outputMaxLength :: Int
    outputMaxLength = 0x7fffffff

    defaultOptions :: Options
    defaultOptions =
        Options { iterations  = 1
                , memory      = 2 ^ (17 :: Int)
                , parallelism = 4
                , variant     = Argon2i
                , version     = Version13
                }

    hash :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray out)
         => Options
         -> password
         -> salt
         -> Int
         -> CryptoFailable out
    hash options password salt outLen
        | saltLen < saltMinLength  = CryptoFailed CryptoError_SaltTooSmall
        | outLen < outputMinLength = CryptoFailed CryptoError_OutputLengthTooSmall
        | outLen > outputMaxLength = CryptoFailed CryptoError_OutputLengthTooBig
        | otherwise                = CryptoPassed $ B.allocAndFreeze outLen $ \out -> do
            res <- B.withByteArray password $ \pPass ->
                   B.withByteArray salt     $ \pSalt ->
                        argon2_hash (iterations options)
                                    (memory options)
                                    (parallelism options)
                                    pPass
                                    (csizeOfInt passwordLen)
                                    pSalt
                                    (csizeOfInt saltLen)
                                    out
                                    (csizeOfInt outLen)
                                    (cOfVariant $ variant options)
                                    (cOfVersion $ version options)
            when (res /= 0) $ error "argon2: hash: internal error"
      where
        saltLen = B.length salt
        passwordLen = B.length password

    data Pass
    data Salt
    data HashOut

    type CVariant = CInt -- valid value is 0 (Argon2d), 1 (Argon2i) and 2 (Argon2id)
    type CVersion = CInt -- valid value is 0x10, 0x13

    cOfVersion :: Version -> CVersion
    cOfVersion Version10 = 0x10
    cOfVersion Version13 = 0x13

    cOfVariant :: Variant -> CVariant
    cOfVariant Argon2d  = 0
    cOfVariant Argon2i  = 1
    cOfVariant Argon2id = 2

    csizeOfInt :: Int -> CSize
    csizeOfInt = fromIntegral

    foreign import ccall unsafe "cryptonite_argon2_hash"
        argon2_hash :: Word32 -> Word32 -> Word32
                    -> Ptr Pass -> CSize
                    -> Ptr Salt -> CSize
                    -> Ptr HashOut -> CSize
                    -> CVariant
                    -> CVersion
                    -> IO CInt