-- | Usage of this module is very simple.  Here is a sample GHCi run:
--
-- @
-- *Crypto.Nonce> g <- new
-- *Crypto.Nonce> nonce128 g
-- \"c\\164\\252\\162f\\207\\245\\ESC`\\180p\\DC4\\234\\223QP\"
-- *Crypto.Nonce> nonce128 g
-- \"\\203C\\190\\138aI\\158\\194\\146\\&7\\208\\&7\\ETX0\\f\\229\"
-- *Crypto.Nonce> nonce128url g
-- \"3RP-iEFT-6NrpCMsxigondMC\"
-- *Crypto.Nonce> nonce128url g
-- \"MVZH3Gi5zSKXJY-_qdtznxla\"
-- *Crypto.Nonce> nonce128url g
-- \"3f3cVNfuZT62-uGco1CBThci\"
-- *Crypto.Nonce> nonce128urlT g
-- \"iGMJyrRkw2QMp09SRy59s4Jx\"
-- *Crypto.Nonce> nonce128urlT g
-- \"WsHs0KwYiex3tsqQZ8b0119_\"
-- *Crypto.Nonce> nonce128urlT g
-- \"JWkLSX7qSFGu1Q3PHuExwurF\"
-- @
--
-- The functions that generate nonces are not pure on purpose,
-- since that makes it a lot harder to reuse the same nonce.
module Crypto.Nonce
  ( Generator
  , new
  , delete
  , withGenerator
  , nonce128
  , nonce128url
  , nonce128urlT
  ) where

import Control.Monad (liftM)
import Control.Monad.IO.Class (MonadIO, liftIO)
import qualified System.Entropy as Entropy
import Data.Typeable (Typeable)
import Control.Monad.IO.Unlift (MonadUnliftIO)
import UnliftIO.Exception (bracket)

import qualified Data.ByteString as B
import qualified Data.ByteString.Base64.URL as B64URL
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE


-- | An encapsulated nonce generator.
newtype Generator = G Entropy.CryptHandle
  deriving (Typeable)

instance Show Generator where
  show _ = "<NonceGenerator>"


-- | Create a new nonce generator using the system entropy.
new :: MonadIO m => m Generator
new = liftM G . liftIO $ Entropy.openHandle


-- | Release the given generator's resources. The generator won't be
-- usable afterwards.
delete :: MonadIO m => Generator -> m ()
delete (G v) = liftIO $ Entropy.closeHandle v


-- | An exception-safe convenience function.
--
-- @
-- withGenerator = bracket new delete
-- @
--
withGenerator :: MonadUnliftIO m => (Generator -> m a) -> m a
withGenerator = bracket new delete


-- | (Internal) Generate the given number of bytes from the DRG.
genBytes :: MonadIO m => Int -> Generator -> m B.ByteString
genBytes n (G v) = liftIO $ Entropy.hGetEntropy v n


-- | Generate a 128 bit nonce as a 'B.ByteString' of 16 bytes.
-- Each byte may have any value from @0@ to @255@.
nonce128 :: MonadIO m => Generator -> m B.ByteString
nonce128 = genBytes 16


-- | Generate a 128 bit nonce as a 'B.ByteString' of 24 bytes.
-- Each byte is either a letter (upper or lowercase), a digit, a
-- dash (@-@) or an underscore (@_@), which is the set of
-- characters from the base64url encoding.  In order to avoid any
-- issues with padding, the generated nonce actually has 144 bits.
nonce128url :: MonadIO m => Generator -> m B.ByteString
nonce128url = liftM B64URL.encode . genBytes 18


-- | Same as 'nonce128url', but returns its result as 'T.Text'
-- instead.
nonce128urlT :: MonadIO m => Generator -> m T.Text
nonce128urlT = liftM TE.decodeUtf8 . nonce128url