{-# LANGUAGE CPP #-}

module Network.QUIC.Crypto.Fusion (
    FusionContext,
    fusionNewContext,
    fusionSetup,
    fusionEncrypt,
    fusionDecrypt,
    Supplement,
    fusionSetupSupplement,
    fusionSetSample,
    fusionGetMask,
    isFusionAvailable,
) where

#ifdef USE_FUSION
import qualified Data.ByteString as BS
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Ptr
import Network.TLS.Extra.Cipher

import Network.QUIC.Crypto.Types
import Network.QUIC.Imports
import Network.QUIC.Types

----------------------------------------------------------------

data FusionContextOpaque
newtype FusionContext = FC (ForeignPtr FusionContextOpaque)

fusionNewContext :: IO FusionContext
fusionNewContext = FC <$> (c_aead_context_new >>= newForeignPtr p_aead_context_free)

----------------------------------------------------------------

fusionSetup :: Cipher -> FusionContext -> Key -> IV -> IO ()
fusionSetup cipher
  | cipher == cipher_TLS13_AES128GCM_SHA256        = fusionSetupAES128
  | cipher == cipher_TLS13_AES256GCM_SHA384        = fusionSetupAES256
  | otherwise                                      = error "fusionSetup"

fusionSetupAES128 :: FusionContext -> Key -> IV -> IO ()
fusionSetupAES128 (FC fctx) (Key key) (IV iv) = withForeignPtr fctx $ \pctx ->
    withByteString key $ \keyp ->
        withByteString iv $ \ivp -> void $ c_aes128gcm_setup pctx 0 keyp ivp

fusionSetupAES256 :: FusionContext -> Key -> IV -> IO ()
fusionSetupAES256 (FC fctx) (Key key) (IV iv) = withForeignPtr fctx $ \pctx ->
    withByteString key $ \keyp ->
        withByteString iv $ \ivp -> void $ c_aes256gcm_setup pctx 0 keyp ivp

----------------------------------------------------------------

fusionEncrypt :: FusionContext -> Supplement
              -> Buffer -> PlainText -> AssDat -> PacketNumber -> IO Int
fusionEncrypt (FC fctx) (SP fsupp) obuf plaintext (AssDat header) pn =
    withForeignPtr fctx $ \pctx -> withForeignPtr fsupp $ \psupp -> do
      withByteString plaintext $ \ibuf ->
        withByteString header $ \abuf -> do
          c_aead_do_encrypt pctx obuf ibuf ilen' pn' abuf alen psupp
          return (ilen + 16) -- fixme
  where
    pn'   = fromIntegral pn
    ilen  = BS.length plaintext
    ilen' = fromIntegral ilen
    alen  = fromIntegral $ BS.length header

fusionDecrypt :: FusionContext
              -> Buffer -> CipherText -> AssDat -> PacketNumber -> IO Int
fusionDecrypt (FC fctx) obuf ciphertext (AssDat header) pn =
    withForeignPtr fctx $ \pctx ->
      withByteString ciphertext $ \ibuf ->
        withByteString header $ \abuf ->
            fromIntegral <$> c_aead_do_decrypt pctx obuf ibuf ilen pn' abuf alen
  where
    pn'  = fromIntegral pn
    ilen = fromIntegral $ BS.length ciphertext
    alen = fromIntegral $ BS.length header

----------------------------------------------------------------

data SupplementOpaque
newtype Supplement = SP (ForeignPtr SupplementOpaque)

fusionSetupSupplement :: Cipher -> Key -> IO Supplement
fusionSetupSupplement cipher (Key hpkey) = withByteString hpkey $ \hpkeyp ->
  SP <$> (c_supplement_new hpkeyp keylen >>= newForeignPtr p_supplement_free)
 where
  keylen
    | cipher == cipher_TLS13_AES128GCM_SHA256 = 16
    | otherwise                               = 32

fusionSetSample :: Supplement -> Buffer -> IO ()
fusionSetSample (SP fsupp) p = withForeignPtr fsupp $ \psupp ->
  c_supplement_set_sample psupp p

fusionGetMask :: Supplement -> IO Buffer
fusionGetMask (SP fsupp) = withForeignPtr fsupp c_supplement_get_mask

isFusionAvailable :: IO Bool
isFusionAvailable = do
    n <- c_ptls_fusion_is_supported_by_cpu
    return $ not (n == 0)

----------------------------------------------------------------

foreign import ccall unsafe "aead_context_new"
    c_aead_context_new :: IO (Ptr FusionContextOpaque)

foreign import ccall unsafe "&aead_context_free"
    p_aead_context_free :: FunPtr (Ptr FusionContextOpaque -> IO ())

foreign import ccall unsafe "aes128gcm_setup"
    c_aes128gcm_setup :: Ptr FusionContextOpaque
                      -> CInt       -- dummy
                      -> Ptr Word8  -- key
                      -> Ptr Word8  -- iv
                      -> IO CInt

foreign import ccall unsafe "aes256gcm_setup"
    c_aes256gcm_setup :: Ptr FusionContextOpaque
                      -> CInt       -- dummy
                      -> Ptr Word8  -- key
                      -> Ptr Word8  -- iv
                      -> IO CInt
{-
foreign import ccall unsafe "aesgcm_dispose_crypto"
    c_aesgcm_dispose_crypto :: FusionContext -> IO ()
-}

foreign import ccall unsafe "aead_do_encrypt"
    c_aead_do_encrypt :: Ptr FusionContextOpaque
                      -> Ptr Word8 -- output
                      -> Ptr Word8 -- input
                      -> CSize     -- input length
                      -> CULong    -- sequence
                      -> Ptr Word8 -- AAD
                      -> CSize     -- AAD length
                      -> Ptr SupplementOpaque
                      -> IO ()

foreign import ccall unsafe "aead_do_decrypt"
    c_aead_do_decrypt :: Ptr FusionContextOpaque
                      -> Ptr Word8 -- output
                      -> Ptr Word8 -- input
                      -> CSize     -- input length
                      -> CULong    -- sequence
                      -> Ptr Word8 -- AAD
                      -> CSize     -- AAD length
                      -> IO CSize

foreign import ccall unsafe "supplement_new"
    c_supplement_new :: Ptr Word8 -> CInt -> IO (Ptr SupplementOpaque)

foreign import ccall unsafe "&supplement_free"
    p_supplement_free :: FunPtr (Ptr SupplementOpaque -> IO ())

foreign import ccall unsafe "supplement_set_sample"
    c_supplement_set_sample :: Ptr SupplementOpaque -> Ptr Word8 -> IO ()

foreign import ccall unsafe "supplement_get_mask"
    c_supplement_get_mask :: Ptr SupplementOpaque -> IO (Ptr Word8)

foreign import ccall unsafe "ptls_fusion_is_supported_by_cpu"
    c_ptls_fusion_is_supported_by_cpu :: IO Int

#else
import Network.QUIC.Crypto.Types
import Network.QUIC.Imports
import Network.QUIC.Types

data FusionContext

fusionNewContext :: IO FusionContext
fusionNewContext = undefined

fusionSetup :: Cipher -> FusionContext -> Key -> IV -> IO ()
fusionSetup = undefined

fusionEncrypt :: FusionContext -> Supplement
              -> Buffer -> PlainText -> AssDat -> PacketNumber -> IO Int
fusionEncrypt = undefined

fusionDecrypt :: FusionContext
              -> Buffer -> CipherText -> AssDat -> PacketNumber -> IO Int
fusionDecrypt = undefined

data Supplement

fusionSetupSupplement :: Cipher -> Key -> IO Supplement
fusionSetupSupplement = undefined

fusionSetSample :: Supplement -> Buffer -> IO ()
fusionSetSample = undefined

fusionGetMask :: Supplement -> IO Buffer
fusionGetMask = undefined

isFusionAvailable :: IO Bool
isFusionAvailable = return False
#endif
