{-|
Module      : Botan.Low.RNG
Description : Random number generators
Copyright   : (c) 2023-2024, Apotheca Labs
              (c) 2024-2025, Haskell Foundation
License     : BSD-3-Clause
Maintainer  : joris@well-typed.com, leo@apotheca.io
Stability   : experimental
Portability : POSIX

Pseudo-random number generation.
-}

module Botan.Low.RNG (

  -- * Random number generators
  -- $introduction

  -- * Usage
  -- $usage

    RNG(..)
  , RNGType
  , withRNG
  , rngInit
  , rngDestroy
  , rngGet
  , systemRNGGet
  , rngReseed
  , rngReseedFromRNG
  , rngAddEntropy

  -- * RNG Types

  , pattern SystemRNG
  , pattern UserRNG
  , pattern UserThreadsafeRNG
  , pattern RDRandRNG

  ) where

import           Botan.Bindings.ConstPtr (ConstPtr (..))
import           Botan.Bindings.RNG
import           Botan.Low.Error.Internal
import           Botan.Low.Internal.ByteString
import           Botan.Low.Remake
import           Data.ByteString (ByteString)
import           Foreign.C.Types
import           Foreign.ForeignPtr
import           Foreign.Ptr

{- $introduction

A `random number generator` is used to generate uniform samples of pseudorandom
bytes.

-}

{- $usage

You can always use the system `RNG`:

> import Botan.Low.RNG
> randomBytes <- systemRNGGet 16

Unless you need a specific `RNG`, it is strongly recommended that you use the
autoseeded `user` RNG.

> import Botan.Low.RNG
> rng <- rngInit "user"
> randomBytes <- rngGet rng 16

You can reseed a generator using the system generator:

> rngReseed rng 64

You can also reseed a generator using a specific generator:

> systemRNG <- rngInit "system"
> rngReseedFromRNG rng systemRNG 64

You can also seed it with your own entropy; this is safe and can never
*decrease* the amount of entropy in the generator.

> rngAddEntropy rng "Fee fi fo fum!"

-}

-- NOTE: Does not take advantage of Remake
-- NOTE: Uses ConstPtr / unConstPtr manually
-- TODO: Take advantage of Remake / better peek / (peekConst or constPeek) functions

newtype RNG = MkRNG { RNG -> ForeignPtr BotanRNGStruct
foreignPtr :: ForeignPtr BotanRNGStruct }

withRNG     :: RNG -> (BotanRNG -> IO a) -> IO a
-- | Destroy a random number generator object immediately
rngDestroy  :: RNG -> IO ()
createRNG   :: (Ptr BotanRNG -> IO CInt) -> IO RNG
(RNG -> (BotanRNG -> IO a) -> IO a
withRNG, RNG -> IO ()
rngDestroy, (Ptr BotanRNG -> IO CInt) -> IO RNG
createRNG)
    = (Ptr BotanRNGStruct -> BotanRNG)
-> (BotanRNG -> Ptr BotanRNGStruct)
-> (ForeignPtr BotanRNGStruct -> RNG)
-> (RNG -> ForeignPtr BotanRNGStruct)
-> FinalizerPtr BotanRNGStruct
-> (RNG -> (BotanRNG -> IO a) -> IO a, RNG -> IO (),
    (Ptr BotanRNG -> IO CInt) -> IO RNG)
forall botan struct object a.
Storable botan =>
(Ptr struct -> botan)
-> (botan -> Ptr struct)
-> (ForeignPtr struct -> object)
-> (object -> ForeignPtr struct)
-> FinalizerPtr struct
-> (object -> (botan -> IO a) -> IO a, object -> IO (),
    (Ptr botan -> IO CInt) -> IO object)
mkBindings Ptr BotanRNGStruct -> BotanRNG
MkBotanRNG (.ptr) ForeignPtr BotanRNGStruct -> RNG
MkRNG (.foreignPtr) FinalizerPtr BotanRNGStruct
botan_rng_destroy

type RNGType = ByteString

pattern SystemRNG           -- ^ system RNG
    ,   UserRNG             -- ^ userspace RNG
    ,   UserThreadsafeRNG   -- ^ userspace RNG, with internal locking
    ,   RDRandRNG           -- ^ directly read RDRAND
    ::  RNGType
pattern $bSystemRNG :: RNGType
$mSystemRNG :: forall {r}. RNGType -> ((# #) -> r) -> ((# #) -> r) -> r
SystemRNG           = BOTAN_RNG_TYPE_SYSTEM
pattern $bUserRNG :: RNGType
$mUserRNG :: forall {r}. RNGType -> ((# #) -> r) -> ((# #) -> r) -> r
UserRNG             = BOTAN_RNG_TYPE_USER
pattern $bUserThreadsafeRNG :: RNGType
$mUserThreadsafeRNG :: forall {r}. RNGType -> ((# #) -> r) -> ((# #) -> r) -> r
UserThreadsafeRNG   = BOTAN_RNG_TYPE_USER_THREADSAFE
pattern $bRDRandRNG :: RNGType
$mRDRandRNG :: forall {r}. RNGType -> ((# #) -> r) -> ((# #) -> r) -> r
RDRandRNG           = BOTAN_RNG_TYPE_RDRAND

{- |
Initialize a random number generator object

rng_type has the possible values:

    - "system": system RNG
    - "user": userspace RNG
    - "user-threadsafe": userspace RNG, with internal locking
    - "rdrand": directly read RDRAND

Set rng_type to null to let the library choose some default.
-}
rngInit
    :: RNGType  -- ^ __rng_type__: type of the rng
    -> IO RNG   -- ^ __rng__
rngInit :: RNGType -> IO RNG
rngInit = ((Ptr BotanRNG -> IO CInt) -> IO RNG)
-> (Ptr BotanRNG -> ConstPtr CChar -> IO CInt) -> RNGType -> IO RNG
forall botan object.
((Ptr botan -> IO CInt) -> IO object)
-> (Ptr botan -> ConstPtr CChar -> IO CInt) -> RNGType -> IO object
mkCreateObjectCString (Ptr BotanRNG -> IO CInt) -> IO RNG
createRNG Ptr BotanRNG -> ConstPtr CChar -> IO CInt
botan_rng_init

-- | Get random bytes from a random number generator
rngGet
    :: RNG              -- ^ __rng__: rng object
    -> Int              -- ^ __out_len__: number of requested bytes
    -> IO ByteString    -- ^ __out__: output buffer of size out_len
rngGet :: RNG -> Int -> IO RNGType
rngGet RNG
rng Int
len = RNG -> (BotanRNG -> IO RNGType) -> IO RNGType
forall a. RNG -> (BotanRNG -> IO a) -> IO a
withRNG RNG
rng ((BotanRNG -> IO RNGType) -> IO RNGType)
-> (BotanRNG -> IO RNGType) -> IO RNGType
forall a b. (a -> b) -> a -> b
$ \ BotanRNG
botanRNG -> do
    Int -> (Ptr Word8 -> IO ()) -> IO RNGType
forall byte. Int -> (Ptr byte -> IO ()) -> IO RNGType
allocBytes Int
len ((Ptr Word8 -> IO ()) -> IO RNGType)
-> (Ptr Word8 -> IO ()) -> IO RNGType
forall a b. (a -> b) -> a -> b
$ \ Ptr Word8
bytesPtr -> do
        HasCallStack => IO CInt -> IO ()
IO CInt -> IO ()
throwBotanIfNegative_ (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$ BotanRNG -> Ptr Word8 -> CSize -> IO CInt
botan_rng_get BotanRNG
botanRNG Ptr Word8
bytesPtr (Int -> CSize
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
len)

-- | Get random bytes from system random number generator
systemRNGGet
    :: Int              -- ^ __out_len__: number of requested bytes
    -> IO ByteString    -- ^ __out__: output buffer of size out_len
systemRNGGet :: Int -> IO RNGType
systemRNGGet Int
len = Int -> (Ptr Word8 -> IO ()) -> IO RNGType
forall byte. Int -> (Ptr byte -> IO ()) -> IO RNGType
allocBytes Int
len ((Ptr Word8 -> IO ()) -> IO RNGType)
-> (Ptr Word8 -> IO ()) -> IO RNGType
forall a b. (a -> b) -> a -> b
$ \ Ptr Word8
bytesPtr -> do
    HasCallStack => IO CInt -> IO ()
IO CInt -> IO ()
throwBotanIfNegative_ (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$ Ptr Word8 -> CSize -> IO CInt
botan_system_rng_get Ptr Word8
bytesPtr (Int -> CSize
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
len)

{- |
Reseed a random number generator

Uses the System_RNG as a seed generator.
-}
rngReseed
    :: RNG  -- ^ __rng__: rng object
    -> Int  -- ^ __bits__: number of bits to reseed with
    -> IO ()
rngReseed :: RNG -> Int -> IO ()
rngReseed RNG
rng Int
bits = RNG -> (BotanRNG -> IO ()) -> IO ()
forall a. RNG -> (BotanRNG -> IO a) -> IO a
withRNG RNG
rng ((BotanRNG -> IO ()) -> IO ()) -> (BotanRNG -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \ BotanRNG
botanRNG -> do
    HasCallStack => IO CInt -> IO ()
IO CInt -> IO ()
throwBotanIfNegative_ (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$ BotanRNG -> CSize -> IO CInt
botan_rng_reseed BotanRNG
botanRNG (Int -> CSize
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
bits)

-- | Reseed a random number generator
rngReseedFromRNG
    :: RNG      -- ^ __rng__: rng object
    -> RNG      -- ^ __source_rng__: the rng that will be read from
    -> Int      -- ^ __bits__: number of bits to reseed with
    -> IO ()
rngReseedFromRNG :: RNG -> RNG -> Int -> IO ()
rngReseedFromRNG RNG
rng RNG
source Int
bits = RNG -> (BotanRNG -> IO ()) -> IO ()
forall a. RNG -> (BotanRNG -> IO a) -> IO a
withRNG RNG
rng ((BotanRNG -> IO ()) -> IO ()) -> (BotanRNG -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \ BotanRNG
botanRNG -> do
    RNG -> (BotanRNG -> IO ()) -> IO ()
forall a. RNG -> (BotanRNG -> IO a) -> IO a
withRNG RNG
source ((BotanRNG -> IO ()) -> IO ()) -> (BotanRNG -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \ BotanRNG
sourcePtr -> do
        HasCallStack => IO CInt -> IO ()
IO CInt -> IO ()
throwBotanIfNegative_ (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$ BotanRNG -> BotanRNG -> CSize -> IO CInt
botan_rng_reseed_from_rng BotanRNG
botanRNG BotanRNG
sourcePtr (Int -> CSize
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
bits)

-- | Add some seed material to a random number generator
rngAddEntropy
    :: RNG          -- ^ __rng__: rng object
    -> ByteString   -- ^ __entropy__: the data to add
    -> IO ()
rngAddEntropy :: RNG -> RNGType -> IO ()
rngAddEntropy RNG
rng RNGType
bytes = RNG -> (BotanRNG -> IO ()) -> IO ()
forall a. RNG -> (BotanRNG -> IO a) -> IO a
withRNG RNG
rng ((BotanRNG -> IO ()) -> IO ()) -> (BotanRNG -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \ BotanRNG
botanRNG -> do
    RNGType -> (Ptr Word8 -> CSize -> IO ()) -> IO ()
forall byte a. RNGType -> (Ptr byte -> CSize -> IO a) -> IO a
asBytesLen RNGType
bytes ((Ptr Word8 -> CSize -> IO ()) -> IO ())
-> (Ptr Word8 -> CSize -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \ Ptr Word8
bytesPtr CSize
bytesLen -> do
        HasCallStack => IO CInt -> IO ()
IO CInt -> IO ()
throwBotanIfNegative_ (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$ BotanRNG -> ConstPtr Word8 -> CSize -> IO CInt
botan_rng_add_entropy BotanRNG
botanRNG (Ptr Word8 -> ConstPtr Word8
forall a. Ptr a -> ConstPtr a
ConstPtr Ptr Word8
bytesPtr) CSize
bytesLen