content: {-# 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
  