packages feed

botan-low-0.0.2.0: src/Botan/Low/Remake.hs

{-|
Module      : Botan.Low.Remake
Description : Low-level binding 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

Generate low-level bindings automatically
-}

module Botan.Low.Remake
( mkBindings
, mkCreateObject
, mkCreateObjects
, mkCreateObjectWith
, mkCreateObjectCString
, mkCreateObjectCString1
, mkCreateObjectCBytes
, mkCreateObjectCBytesLen
, mkCreateObjectCBytesLen1
, mkWithObjectAction
, mkWithObjectGetterCBytesLen
, mkWithObjectGetterCBytesLen1
, mkWithObjectSetterCString
, mkWithObjectSetterCBytesLen
) where

import Botan.Low.Prelude
import Botan.Low.Error

import qualified Data.ByteString.Internal as ByteString

import Botan.Low.Make (allocBytesQuerying)

-- ByteString Helpers

-- NOTE: Was allocBytes
createByteString :: Int -> (Ptr byte -> IO ()) -> IO ByteString
createByteString sz f = ByteString.create sz (f . castPtr)

-- NOTE: Was allocBytesWith
-- createByteString' :: Int -> (Ptr byte -> IO a) -> IO (a, ByteString)

-- NOTE: Was allocBytesQuerying
-- createByteStringQuerying :: (Ptr byte -> Ptr CSize -> IO CInt) -> IO ByteString
-- createByteStringQuerying fn = do
--     alloca $ \ szPtr -> do
--         -- TODO: Maybe poke szPtr 0 for extra safety in cas its not initially zero
--         code <- fn nullPtr szPtr
--         case code of
--             InsufficientBufferSpace -> do
--                 sz <- fromIntegral <$> peek szPtr
--                 allocBytes sz $ \ outPtr -> throwBotanIfNegative_ $ fn outPtr szPtr
--             _                       -> do
--                 throwBotanError code

-- NOTE: Was allocBytesQueryingCString
-- NOTE: Does not check length of taken string, vulnerable to null byte injection
-- createByteStringQueryingCString :: (Ptr byte -> Ptr CSize -> IO CInt) -> IO ByteString
-- createByteStringQueryingCString action = do
--     cstring <- allocBytesQuerying action
--     return $!! ByteString.takeWhile (/= 0) cstring

--

-- type NewObject      object botan = botan -> IO object
-- type WithObject     object botan = (forall a . object -> (botan -> IO a) -> IO a)
-- type DestroyObject  object botan = object -> IO ()
-- type CreateObject   object botan = (Ptr botan -> IO CInt) -> IO object
-- type CreateObjects  object botan = (Ptr botan -> Ptr CSize -> IO CInt) -> IO object

-- Example usage
{-
newtype RNG = MkRNG { getRNGForeignPtr :: ForeignPtr BotanRNGStruct }

newRNG      :: BotanRNG -> IO RNG
withRNG     :: RNG -> (BotanRNG -> IO a) -> IO a
rngDestroy  :: RNG -> IO ()
createRNG   :: (Ptr BotanRNG -> IO CInt) -> IO RNG
(newRNG, withRNG, rngDestroy, createRNG, _)
    = mkBindings MkBotanRNG runBotanRNG MkRNG getRNGForeignPtr botan_rng_destroy

rngInit :: RNGType -> IO RNG
rngInit name = asCString name $ \ namePtr -> do
    createRNG $ \ outPtr -> botan_rng_init outPtr (ConstPtr namePtr)
-}
mkBindings
    ::  (Storable botan)
    =>  (Ptr struct -> botan)                                   -- mkBotan
    ->  (botan -> Ptr struct)                                   -- runBotan
    ->  (ForeignPtr struct -> object)                           -- mkForeign
    ->  (object -> ForeignPtr struct)                           -- runForeign
    ->  FinalizerPtr struct                                     -- destroy / finalizer
    ->  (   botan -> IO object                                  -- newObject
        ,   object -> (botan -> IO a) -> IO a                   -- withObject
        ,   object -> IO ()                                     -- destroyObject
        ,   (Ptr botan -> IO CInt) -> IO object                 -- createObject
        ,   (Ptr botan -> Ptr CSize -> IO CInt) -> IO [object]  -- createObjects
        )
mkBindings mkBotan runBotan mkForeign runForeign destroy = bindings where
    bindings = (newObject, withObject, objectDestroy, createObject, createObjects)
    newObject botan = do
        foreignPtr <- newForeignPtr destroy (runBotan botan)
        return $ mkForeign foreignPtr
    withObject object f = withForeignPtr (runForeign object) (f . mkBotan)
    objectDestroy object = finalizeForeignPtr (runForeign object)
    -- NOTE: This ^ is really a Haskell finalizer
    --  We could include the actual C++ botan destructor instead of indirectly omitting it:
    --      objectFinalize obj = new stable foreign ptr ... destroy
    --      objectDestroy obj = withObject obj destroy
    createObject = mkCreateObject newObject
    createObjects = mkCreateObjects newObject

{-
Create functions
-}

-- TODO: Rename mkCreate
mkCreateObject
    :: (Storable botan)
    => (botan -> IO object)
    -> (Ptr botan-> IO CInt)
    -> IO object
mkCreateObject newObject init = mask_ $ alloca $ \ outPtr -> do
        throwBotanIfNegative_ $ init outPtr
        out <- peek outPtr
        newObject out

-- TODO: Rename mkCreates
mkCreateObjects
    :: (Storable botan)
    => (botan -> IO object)
    -> (Ptr botan -> Ptr CSize -> IO CInt)
    -> IO [object]
mkCreateObjects newObject inits = mask_ $ alloca $ \ szPtr -> do
        code <- inits nullPtr szPtr
        case code of
            InsufficientBufferSpace -> do
                sz <- fromIntegral <$> peek szPtr
                allocaArray sz $ \ arrPtr -> do
                    throwBotanIfNegative_ $ inits arrPtr szPtr
                    outs <- peekArray sz arrPtr
                    forM outs newObject
            _ -> throwBotanError code

mkCreateObjectWith
    :: ((Ptr botan -> IO CInt) -> IO object)
    -> (arg -> (carg -> IO object) -> IO object)
    -> (Ptr botan -> carg -> IO CInt)
    -> arg
    -> IO object
mkCreateObjectWith createObject withArg init arg = withArg arg $ \ carg -> do
    createObject $ \ outPtr -> init outPtr carg

-- TODO: Rename mkCreateCString
mkCreateObjectCString
    :: ((Ptr botan -> IO CInt) -> IO object)
    -> (Ptr botan -> ConstPtr CChar -> IO CInt)
    -> ByteString
    -> IO object
-- mkCreateObjectCString createObject init cstr = withCString cstr $ \ namePtr -> do
--     createObject $ \ outPtr -> init outPtr (ConstPtr namePtr)
mkCreateObjectCString createObject = mkCreateObjectWith createObject withConstCString

-- TODO: Rename mkCreateCString1
mkCreateObjectCString1
    :: ((Ptr botan -> IO CInt) -> IO object)
    -> (Ptr botan -> ConstPtr CChar -> a -> IO CInt)
    -> ByteString
    -> a
    -> IO object
mkCreateObjectCString1 createObject init str a = withCString str $ \ cstr -> do
    createObject $ \ outPtr -> init outPtr (ConstPtr cstr) a

-- | You probably want mkCreateObjectCBytesLen; this is for functions that
-- expect a bytestring of known exact length.
mkCreateObjectCBytes
    :: ((Ptr botan -> IO CInt) -> IO object)
    -> (Ptr botan -> ConstPtr Word8 -> IO CInt)
    -> ByteString
    -> IO object
-- TODO: Rename mkCreateCBytes
mkCreateObjectCBytes createObject init bytes = withCBytes bytes $ \ cbytes -> do
    createObject $ \ out -> init out (ConstPtr cbytes)

-- TODO: Rename mkCreateCBytesLen
mkCreateObjectCBytesLen
    :: ((Ptr botan -> IO CInt) -> IO object)
    -> (Ptr botan -> ConstPtr Word8 -> CSize -> IO CInt)
    -> ByteString
    -> IO object
mkCreateObjectCBytesLen createObject init bytes = withCBytesLen bytes $ \ (cbytes,len) -> do
    createObject $ \ out -> init out (ConstPtr cbytes) (fromIntegral len)

mkCreateObjectCBytesLen1
    :: ((Ptr botan -> IO CInt) -> IO object)
    -> (Ptr botan -> ConstPtr Word8 -> CSize -> a -> IO CInt)
    -> ByteString
    -> a
    -> IO object
mkCreateObjectCBytesLen1 createObject init bytes a = withCBytesLen bytes $ \ (cbytes,len) -> do
    createObject $ \ out -> init out (ConstPtr cbytes) (fromIntegral len) a

{-
Action
-}

-- TODO: Rename mkAction
mkWithObjectAction
    :: (forall a . object -> (botan -> IO a) -> IO a)
    -> (botan -> IO CInt)
    -> object
    -> IO ()
mkWithObjectAction withObject action obj = withObject obj $ \ cobj -> do
    throwBotanIfNegative_ $ action cobj

{-
Getters
-}

-- TODO: getter parameter order may be improper - switch up if problematic
mkWithObjectGetterCBytesLen
    :: (forall a . object -> (botan -> IO a) -> IO a)
    -> (botan -> Ptr Word8 -> Ptr CSize -> IO CInt)
    -> object
    -> IO ByteString
mkWithObjectGetterCBytesLen withObject getter obj = withObject obj $ \ cobj -> do
    allocBytesQuerying $ \ outPtr outLen -> getter
        cobj
        outPtr
        outLen

-- TODO: getter parameter order may be improper - switch up if problematic
mkWithObjectGetterCBytesLen1
    :: (forall a . object -> (botan -> IO a) -> IO a)
    -> (botan -> a -> Ptr Word8 -> Ptr CSize -> IO CInt)
    -> object
    -> a
    -> IO ByteString
mkWithObjectGetterCBytesLen1 withObject getter obj a = withObject obj $ \ cobj -> do
    allocBytesQuerying $ \ outPtr outLen -> getter
        cobj
        a
        outPtr
        outLen

{-
Setters
-}

-- TODO: Rename mkSetterCString
mkWithObjectSetterCString
    :: (forall a . object -> (botan -> IO a) -> IO a)
    -> (botan -> ConstPtr CChar -> IO BotanErrorCode)
    -> object
    -> ByteString
    -> IO ()
mkWithObjectSetterCString withObject setter obj str = withObject obj $ \ cobj -> do
    withCString str $ \ cstr -> do
        throwBotanIfNegative_ $ setter cobj (ConstPtr cstr)

-- Replaces mkSetBytesLen
-- TODO: Rename mkSetterCBytesLen
mkWithObjectSetterCBytesLen
    :: (forall a . object -> (botan -> IO a) -> IO a)
    -> (botan -> ConstPtr Word8 -> CSize -> IO BotanErrorCode)
    -> object
    -> ByteString
    -> IO ()
mkWithObjectSetterCBytesLen withObject setter obj bytes = withObject obj $ \ cobj -> do
    withCBytesLen bytes $ \ (cbytes,len) -> do
        throwBotanIfNegative_ $ setter cobj (ConstPtr cbytes) (fromIntegral len)