{-|
Module      : Crypto.Hash.Fold
Copyright   : (c) 2016, Drew Hess
License     : BSD3
Maintainer  : Drew Hess <src@drewhess.com>
Stability   : experimental

This module provides efficient and streaming left folds of
cryptographic hashes over byte arrays. The folds are compatible with
the 'Fold' type from the @foldl@ package and can be combined with
other 'Fold's in applicative style.

-}

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE Trustworthy #-}

module Crypto.Hash.Fold
       ( -- * Folds
         hash
       , hashUpdate
       , hashWith
         -- * Hashing
       , hashlazy
       ) where

import qualified Data.ByteString.Lazy as LBS
import Control.Foldl (Fold(..))
import qualified Control.Foldl.ByteString as L (fold)
import Crypto.Hash (Context, Digest, HashAlgorithm, hashFinalize, hashInit)
import qualified Crypto.Hash as Hash (hashUpdate)
import Data.ByteArray (ByteArrayAccess)

-- Strict in the accumulator.
hashUpdate' :: (HashAlgorithm a, ByteArrayAccess ba) => Context a -> ba -> Context a
hashUpdate' !ctx = Hash.hashUpdate ctx
{-# INLINEABLE hashUpdate' #-}

-- | Update a 'Context' with a byte array.
hashUpdate :: (HashAlgorithm a, ByteArrayAccess ba) => Context a -> Fold ba (Context a)
hashUpdate ctx = Fold hashUpdate' ctx id
{-# INLINEABLE hashUpdate #-}

-- | Return the 'Digest' of a byte array.
hash :: (HashAlgorithm a, ByteArrayAccess ba) => Fold ba (Digest a)
hash = hashFinalize <$> hashUpdate hashInit
{-# INLINEABLE hash #-}

-- | Return the 'Digest' of a byte array, with an explicit algorithm
-- parameter.
hashWith :: (HashAlgorithm alg, ByteArrayAccess ba) => alg -> Fold ba (Digest alg)
hashWith = const hash
{-# INLINEABLE hashWith #-}

-- | Compute the 'Digest' of a lazy 'LBS.ByteString' using a strict left
-- fold.
hashlazy :: HashAlgorithm a => LBS.ByteString -> Digest a
hashlazy = L.fold hash
