I am working on bindings to the Botan cryptography library, but need some help with the FFI. Context: I have done a fair amount of C in the past, but it has been a while (10 years), so I am a bit rusty. I have messed around with Foreign.Ptr
and the FFI before, but nothing this complex before.
I need to implement the following C interface, using the Haskell FFI:
typedef opaque *botan_hash_t
An opaque data type for a hash. Don’t mess with it.
int botan_hash_init(botan_hash_t hash, const char *hash_name, uint32_t flags)
Creates a hash of the given name, e.g., “SHA-384”.
Flags should always be zero in this version of the API.
int botan_hash_destroy(botan_hash_t hash)
Destroy the object created by botan_hash_init.
int botan_hash_name(botan_hash_t hash, char *name, size_t *name_len)
Write the name of the hash function to the provided buffer.
I have found Foreign.ForeignPtr
which sounds like precisely what I need to run the finalizer automatically upon garbage collect, but I do not know how to represent the botan_hash_t
object properly, nor make it Storable
. I have read as many docs as I can find, but the recent degradation of Google and Reddit has make locating example material difficult, so I am turning here.
This may rightfully horrify you, but so far, I have:
type OpaqueHash = Ptr () -- ??? I know this is wrong
newtype Hash = Hash (ForeignPtr OpaqueHash) -- I suspect this is wrong
foreign import ccall unsafe botan_hash_init :: Ptr OpaqueHash -> Ptr CChar -> Word32 -> IO BotanErrorCode
foreign import ccall "&botan_hash_destroy" botan_hash_destroy :: FinalizerPtr OpaqueHash
hashInit :: ByteString -> IO Hash
hashInit name = do
opaqueHash <- mallocForeignPtr
withForeignPtr opaqueHash $ \ opaqueHash' -> do
withByteArray name $ \ name' -> do
throwBotanIfNegative_ $ botan_hash_init opaqueHash' name' 0
addForeignPtrFinalizer botan_hash_destroy opaqueHash
return $ Hash opaqueHash
foreign import ccall unsafe botan_hash_name :: Ptr OpaqueHash -> Ptr CChar -> Ptr CSize -> IO BotanErrorCode
hashName :: Hash -> IO ByteString
hashName (Hash opaqueHash) = alloca $ \ szPtr -> do
bs <- ByteArray.alloc 64 $ \ bytes -> do
withForeignPtr opaqueHash $ \ opaqueHash' -> do
throwBotanIfNegative_ $ botan_hash_name opaqueHash' bytes szPtr
sz <- peek szPtr
return $ ByteArray.take (fromIntegral sz) bs
This compiles, and h <- hashInit "SHA-256"
runs. However, it appears that it is not initialized properly, because hashName h
will (correctly) trigger throwBotanIfNegative_
- but it can’t be too far off, because it doesn’t segfault or get into serious trouble. What am I doing wrong here, and how do I properly represent the OpaqueHash
type?
Additionally, as another knowledge check, I would like to implement this, due to the additional indirection at botan_hash_t *dest
. I will need to be able to implement many similar cases.
int botan_hash_copy_state(botan_hash_t *dest, const botan_hash_t source)
Copies the state of the hash object to a new hash object.
Are there any particularly good libraries or comprehensive examples of ForeignPtr
usage with opaque C data types? If you have experience with implementing similar FFI binding needs, I wouldn’t mind a chance to pick your brain and ask more pointed questions.