packages feed

tigerbeetle-hs-0.1.0.0: src/Database/TigerBeetle/Internal/FFI/Client.hsc

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RecordWildCards #-}

module Database.TigerBeetle.Internal.FFI.Client where

import Data.Word
import Foreign.Ptr
import Foreign.Storable
import Foreign.C.String
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Data.Vector (Vector)
import Data.Vector qualified as V
import Database.TigerBeetle.ClusterId
import Database.TigerBeetle.Internal.FFI.Client.ClusterId

#include "tb_client.h"

data TBClient = TBClient
    { tbClientOpaque :: Vector Word64
    }
    deriving (Show, Eq)

instance Storable TBClient where
    sizeOf _ = #{size tb_client_t}

    alignment _ = #{alignment tb_client_t}

    peek ptr = do
      let opaquePtr = #{ptr tb_client_t, opaque} ptr
      tbClientOpaque <- V.generateM 4 (\i -> peekByteOff opaquePtr (i * 8))
      pure TBClient{..}

    poke ptr client = do
      let opaquePtr = #{ptr tb_client_t, opaque} ptr
      V.iforM_ client.tbClientOpaque $ \i val -> pokeByteOff opaquePtr (i * 8) val

data TBInitStatus =
      Success
    | Unexpected
    | OutOfMemory
    | AddressInvalid
    | AddressLimitExceeded
    | SystemResources
    | NetworkSubsystem
    deriving (Eq, Show)

instance Enum TBInitStatus where
    fromEnum Success              = #const TB_INIT_SUCCESS
    fromEnum Unexpected           = #const TB_INIT_UNEXPECTED
    fromEnum OutOfMemory          = #const TB_INIT_OUT_OF_MEMORY
    fromEnum AddressInvalid       = #const TB_INIT_ADDRESS_INVALID
    fromEnum AddressLimitExceeded = #const TB_INIT_ADDRESS_LIMIT_EXCEEDED
    fromEnum SystemResources      = #const TB_INIT_SYSTEM_RESOURCES
    fromEnum NetworkSubsystem     = #const TB_INIT_NETWORK_SUBSYSTEM

    toEnum (#const TB_INIT_SUCCESS)                = Success
    toEnum (#const TB_INIT_UNEXPECTED)             = Unexpected
    toEnum (#const TB_INIT_OUT_OF_MEMORY)          = OutOfMemory
    toEnum (#const TB_INIT_ADDRESS_INVALID)        = AddressInvalid
    toEnum (#const TB_INIT_ADDRESS_LIMIT_EXCEEDED) = AddressLimitExceeded
    toEnum (#const TB_INIT_SYSTEM_RESOURCES)       = SystemResources
    toEnum (#const TB_INIT_NETWORK_SUBSYSTEM)      = NetworkSubsystem
    toEnum unmatched = error $ "TBInitStatus.toEnum: Cannot match " ++ show unmatched

data TBClientStatus =
      ClientOk
    | ClientInvalid
    deriving (Eq, Show)

instance Enum TBClientStatus where
    fromEnum ClientOk      = #const TB_CLIENT_OK
    fromEnum ClientInvalid = #const TB_CLIENT_INVALID

    toEnum (#const TB_CLIENT_OK)      = ClientOk
    toEnum (#const TB_CLIENT_INVALID) = ClientInvalid
    toEnum unmatched = error $ "TBClientStatus.toEnum: Cannot match " ++ show unmatched

data TBOperation =
      Pulse
    | CreateAccounts
    | CreateTransfers
    | LookupAccounts
    | LookupTransfers
    | GetAccountTransfers
    | GetAccountBalances
    | QueryAccounts
    | QueryTransfers
    | GetEvents
    deriving (Eq, Show)

instance Enum TBOperation where
    fromEnum Pulse               = #const TB_OPERATION_PULSE
    fromEnum CreateAccounts      = #const TB_OPERATION_CREATE_ACCOUNTS
    fromEnum CreateTransfers     = #const TB_OPERATION_CREATE_TRANSFERS
    fromEnum LookupAccounts      = #const TB_OPERATION_LOOKUP_ACCOUNTS
    fromEnum LookupTransfers     = #const TB_OPERATION_LOOKUP_TRANSFERS
    fromEnum GetAccountTransfers = #const TB_OPERATION_GET_ACCOUNT_TRANSFERS
    fromEnum GetAccountBalances  = #const TB_OPERATION_GET_ACCOUNT_BALANCES
    fromEnum QueryAccounts       = #const TB_OPERATION_QUERY_ACCOUNTS
    fromEnum QueryTransfers      = #const TB_OPERATION_QUERY_TRANSFERS
    fromEnum GetEvents           = #const TB_OPERATION_GET_EVENTS

    toEnum (#const TB_OPERATION_PULSE)                 = Pulse
    toEnum (#const TB_OPERATION_CREATE_ACCOUNTS)       = CreateAccounts
    toEnum (#const TB_OPERATION_CREATE_TRANSFERS)      = CreateTransfers
    toEnum (#const TB_OPERATION_LOOKUP_ACCOUNTS)       = LookupAccounts
    toEnum (#const TB_OPERATION_LOOKUP_TRANSFERS)      = LookupTransfers
    toEnum (#const TB_OPERATION_GET_ACCOUNT_TRANSFERS) = GetAccountTransfers
    toEnum (#const TB_OPERATION_GET_ACCOUNT_BALANCES)  = GetAccountBalances
    toEnum (#const TB_OPERATION_QUERY_ACCOUNTS)        = QueryAccounts
    toEnum (#const TB_OPERATION_QUERY_TRANSFERS)       = QueryTransfers
    toEnum (#const TB_OPERATION_GET_EVENTS)            = GetEvents
    toEnum unmatched = error $ "TBOperation.toEnum: Cannot match " ++ show unmatched

marshallTBOperation :: TBOperation -> Word8
marshallTBOperation = fromIntegral . fromEnum

unmarshallTBOperation :: Word8 -> TBOperation
unmarshallTBOperation = toEnum . fromIntegral

data TBPacketStatus =
      Ok
    | TooMuchData
    | ClientEvicted
    | ClientReleaseTooLow
    | ClientReleaseTooHigh
    | ClientShutdown
    | InvalidOperation
    | InvalidDataSize
    deriving (Eq, Show)

instance Enum TBPacketStatus where
    fromEnum Ok                   = #const TB_PACKET_OK
    fromEnum TooMuchData          = #const TB_PACKET_TOO_MUCH_DATA
    fromEnum ClientEvicted        = #const TB_PACKET_CLIENT_EVICTED
    fromEnum ClientReleaseTooLow  = #const TB_PACKET_CLIENT_RELEASE_TOO_LOW
    fromEnum ClientReleaseTooHigh = #const TB_PACKET_CLIENT_RELEASE_TOO_HIGH
    fromEnum ClientShutdown       = #const TB_PACKET_CLIENT_SHUTDOWN
    fromEnum InvalidOperation     = #const TB_PACKET_INVALID_OPERATION
    fromEnum InvalidDataSize      = #const TB_PACKET_INVALID_DATA_SIZE

    toEnum (#const TB_PACKET_OK)                      = Ok
    toEnum (#const TB_PACKET_TOO_MUCH_DATA)           = TooMuchData
    toEnum (#const TB_PACKET_CLIENT_EVICTED)          = ClientEvicted
    toEnum (#const TB_PACKET_CLIENT_RELEASE_TOO_LOW)  = ClientReleaseTooLow
    toEnum (#const TB_PACKET_CLIENT_RELEASE_TOO_HIGH) = ClientReleaseTooHigh
    toEnum (#const TB_PACKET_CLIENT_SHUTDOWN)         = ClientShutdown
    toEnum (#const TB_PACKET_INVALID_OPERATION)       = InvalidOperation
    toEnum (#const TB_PACKET_INVALID_DATA_SIZE)       = InvalidDataSize
    toEnum unmatched = error $ "TBPacketStatus.toEnum: Cannot match " ++ show unmatched

marshallTBPacketStatus :: TBPacketStatus -> Word8
marshallTBPacketStatus = fromIntegral . fromEnum

unmarshallTBPacketStatus :: Word8 -> TBPacketStatus
unmarshallTBPacketStatus = toEnum . fromIntegral

data TBPacket = TBPacket
    { tbPacketUserData   :: Ptr ()
    , tbPacketData       :: Ptr ()
    , tbPacketDataSize   :: Word32
    , tbPacketUserTag    :: Word16
    , tbPacketOperation  :: TBOperation
    , tbPacketStatus     :: TBPacketStatus
    , tbPacketOpaque     :: Vector Word8
    }
    deriving (Show, Eq)

instance Storable TBPacket where
    sizeOf _ = #{size tb_packet_t}

    alignment _ = #{alignment tb_packet_t}

    peek ptr = do
      tbPacketUserData   <- #{peek tb_packet_t, user_data} ptr
      tbPacketData       <- #{peek tb_packet_t, data} ptr
      tbPacketDataSize   <- #{peek tb_packet_t, data_size} ptr
      tbPacketUserTag    <- #{peek tb_packet_t, user_tag} ptr
      tbPacketOperation  <- unmarshallTBOperation <$> #{peek tb_packet_t, operation} ptr
      tbPacketStatus     <- unmarshallTBPacketStatus <$> #{peek tb_packet_t, status} ptr
      let opaquePtr = #{ptr tb_packet_t, opaque} ptr
      tbPacketOpaque     <- V.generateM 32 (\i -> peekByteOff opaquePtr i)
      pure TBPacket{..}

    poke ptr packet = do
        #{poke tb_packet_t, user_data} ptr packet.tbPacketUserData
        #{poke tb_packet_t, data} ptr packet.tbPacketData
        #{poke tb_packet_t, data_size} ptr packet.tbPacketDataSize
        #{poke tb_packet_t, user_tag} ptr packet.tbPacketUserTag
        #{poke tb_packet_t, operation} ptr (marshallTBOperation packet.tbPacketOperation)
        #{poke tb_packet_t, status} ptr (marshallTBPacketStatus packet.tbPacketStatus)
        let opaquePtr = #{ptr tb_packet_t, opaque} ptr
        V.iforM_ packet.tbPacketOpaque $ \i val -> pokeByteOff opaquePtr i val

type TBCompletionContext = CUIntPtr

-- TODO: add comments explaining what these represent, asked a question in TB slack
type TBCompletionCallback =
  TBCompletionContext ->
  Ptr TBPacket ->
  Word64 ->
  Ptr Word8 ->
  Word32 ->
  IO ()

foreign import ccall "wrapper"
    makeCompletionCallback :: TBCompletionCallback -> IO (FunPtr TBCompletionCallback)

foreign import ccall "tb_client.h tb_client_init"
    c_tb_client_init
      :: Ptr TBClient
      -> Ptr Word8
      -> CString
      -> Word32
      -> CUIntPtr
      -> FunPtr TBCompletionCallback
      -> IO Word32

tbClientInit
  :: Ptr TBClient
  -> ClusterId
  -> CString
  -> Word32
  -> TBCompletionContext
  -> FunPtr TBCompletionCallback
  -> IO TBInitStatus
tbClientInit client clusterId addr addrLen ctx cb =
    withClusterIdPointer clusterId $ \clusterIdPtr ->
       toEnum . fromIntegral <$> c_tb_client_init client clusterIdPtr addr addrLen ctx cb

foreign import ccall "tb_client.h tb_client_init_echo"
    c_tb_client_init_echo
      :: Ptr TBClient
      -> Ptr Word8
      -> CString
      -> Word32
      -> TBCompletionContext
      -> FunPtr TBCompletionCallback
      -> IO Word32

tbClientInitEcho
  :: Ptr TBClient
  -> ClusterId
  -> CString
  -> Word32
  -> TBCompletionContext
  -> FunPtr TBCompletionCallback
  -> IO TBInitStatus
tbClientInitEcho client clusterId addr addrLen ctx cb =
    withClusterIdPointer clusterId $ \clusterIdPtr ->
       toEnum . fromIntegral <$> c_tb_client_init_echo client clusterIdPtr addr addrLen ctx cb

foreign import ccall "tb_client.h tb_client_completion_context"
    c_tb_client_completion_context :: Ptr TBClient -> Ptr TBCompletionContext -> IO Word32

clientCompletionContext :: Ptr TBClient -> IO (TBClientStatus, TBCompletionContext)
clientCompletionContext client = alloca $ \ctxPtr -> do
    status <- c_tb_client_completion_context client ctxPtr
    ctx <- peek ctxPtr
    pure (toEnum $ fromIntegral status, ctx)

foreign import ccall "tb_client.h tb_client_submit"
    c_tb_client_submit :: Ptr TBClient -> Ptr TBPacket -> IO Word32

tbClientSubmit :: Ptr TBClient -> Ptr TBPacket -> IO TBClientStatus
tbClientSubmit client packet = toEnum . fromIntegral <$> c_tb_client_submit client packet

foreign import ccall "tb_client.h tb_client_deinit"
    c_tb_client_deinit :: Ptr TBClient -> IO Word32

type TBClientFinalizerCallback
  = Ptr TBClient -> IO ()

foreign import ccall "wrapper"
    makeClientFinalizer :: TBClientFinalizerCallback -> IO (FunPtr TBClientFinalizerCallback)

tbClientDeinit :: Ptr TBClient -> IO TBClientStatus
tbClientDeinit client = toEnum . fromIntegral <$> c_tb_client_deinit client