-- |
-- Module      : Crypto.Random.API
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : Good

module Crypto.Random.API
    ( CPRG(..)
    , genRandomBytes
    , withRandomBytes
    , getSystemEntropy
    ) where

import qualified Data.ByteString as B
import Data.ByteString (ByteString)
import System.Entropy (getEntropy)

-- | A class of Cryptographic Secure Random generator.
--
-- The main difference with the generic haskell RNG is that
-- it return bytes instead of integer.
--
-- It is quite similar to the CryptoRandomGen class in crypto-api
-- except that error are not returned to the user. Instead
-- the user is suppose to handle reseeding by using the NeedReseed
-- and SupplyEntropy methods. For other type of errors, the user
-- is expect to generate bytes with the parameters bounds explicity
-- defined here.
-- 
-- The CPRG need to be able to generate up to 2^20 bytes in one call,
--
class CPRG g where
    -- | Provide a way to query the CPRG to calculate when new entropy
    -- is required to be supplied so the CPRG doesn't repeat output, and
    -- break assumptions. This returns the number of bytes before
    -- which supply entropy should have been called.
    cprgNeedReseed    :: g -> Int

    -- | Supply entropy to the CPRG, that can be used now or later
    -- to reseed the CPRG. This should be used in conjunction to
    -- NeedReseed to know when to supply entropy.
    cprgSupplyEntropy :: g -> ByteString -> g

    -- | Generate bytes using the CPRG and the number specified.
    --
    -- For user of the API, it's recommended to use genRandomBytes
    -- instead of this method directly.
    cprgGenBytes      :: g -> Int -> (ByteString, g)

-- | Generate bytes using the cprg in parameter.
-- 
-- arbitrary limit the number of bytes that can be generated in
-- one go to 10mb.
genRandomBytes :: CPRG g => g   -- ^ CPRG to use
                         -> Int -- ^ number of bytes to return
                         -> (ByteString, g)
genRandomBytes rng len
    | len < 0    = error "genBytes: cannot request negative amount of bytes."
    | len > 2^20 = error "genBytes: cannot request more than 1mb of bytes in one go."
    | len == 0   = (B.empty, rng)
    | otherwise  = cprgGenBytes rng len

-- | this is equivalent to using Control.Arrow 'first' with genBytes.
--
-- namely it generate @len bytes and map the bytes to the function @f
withRandomBytes :: CPRG g => g -> Int -> (ByteString -> a) -> (a, g)
withRandomBytes rng len f = (f bs, rng')
    where (bs, rng') = genRandomBytes rng len

-- | Return system entropy using the entropy package 'getEntropy'
getSystemEntropy :: Int -> IO ByteString
getSystemEntropy = getEntropy