/*
* (C) 2015,2017 Jack Lloyd
* (C) 2017 Ribose Inc
* (C) 2018 René Korthaus, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/ffi.h>

#include <botan/assert.h>
#include <botan/ec_group.h>
#include <botan/hash.h>
#include <botan/mem_ops.h>
#include <botan/pem.h>
#include <botan/internal/ffi_ec.h>
#include <botan/internal/ffi_mp.h>
#include <botan/internal/ffi_pkey.h>
#include <botan/internal/ffi_rng.h>
#include <botan/internal/ffi_util.h>

#if defined(BOTAN_HAS_DL_GROUP)
   #include <botan/dl_group.h>
#endif

#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
   #include <botan/ecc_key.h>
#endif

#if defined(BOTAN_HAS_RSA)
   #include <botan/rsa.h>
#endif

#if defined(BOTAN_HAS_ELGAMAL)
   #include <botan/elgamal.h>
#endif

#if defined(BOTAN_HAS_DSA)
   #include <botan/dsa.h>
#endif

#if defined(BOTAN_HAS_ECDSA)
   #include <botan/ecdsa.h>
#endif

#if defined(BOTAN_HAS_SM2)
   #include <botan/sm2.h>
#endif

#if defined(BOTAN_HAS_ECDH)
   #include <botan/ecdh.h>
#endif

#if defined(BOTAN_HAS_X25519)
   #include <botan/x25519.h>
#endif

#if defined(BOTAN_HAS_X448)
   #include <botan/x448.h>
#endif

#if defined(BOTAN_HAS_ED25519)
   #include <botan/ed25519.h>
#endif

#if defined(BOTAN_HAS_ED448)
   #include <botan/ed448.h>
#endif

#if defined(BOTAN_HAS_DIFFIE_HELLMAN)
   #include <botan/dh.h>
#endif

#if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S)
   #include <botan/kyber.h>
#endif

#if defined(BOTAN_HAS_ML_KEM)
   #include <botan/ml_kem.h>
#endif

#if defined(BOTAN_HAS_FRODOKEM)
   #include <botan/frodokem.h>
#endif

#if defined(BOTAN_HAS_ML_DSA)
   #include <botan/ml_dsa.h>
#endif

#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE)
   #include <botan/slh_dsa.h>
#endif

#if defined(BOTAN_HAS_CLASSICMCELIECE)
   #include <botan/cmce.h>
#endif

namespace {

#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)

// These are always called within an existing try/catch block

template <class ECPrivateKey_t>
int privkey_load_ec(std::unique_ptr<ECPrivateKey_t>& key, const Botan::BigInt& scalar, const char* curve_name) {
   if(curve_name == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   if(!Botan::EC_Group::supports_named_group(curve_name)) {
      return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
   }

   Botan::Null_RNG null_rng;
   const auto grp = Botan::EC_Group::from_name(curve_name);
   key.reset(new ECPrivateKey_t(null_rng, grp, scalar));
   return BOTAN_FFI_SUCCESS;
}

template <class ECPublicKey_t>
int pubkey_load_ec(std::unique_ptr<ECPublicKey_t>& key,
                   const Botan::BigInt& public_x,
                   const Botan::BigInt& public_y,
                   const char* curve_name) {
   if(curve_name == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   if(!Botan::EC_Group::supports_named_group(curve_name)) {
      return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
   }

   const auto group = Botan::EC_Group::from_name(curve_name);

   if(auto pt = Botan::EC_AffinePoint::from_bigint_xy(group, public_x, public_y)) {
      key.reset(new ECPublicKey_t(group, pt.value()));
      return BOTAN_FFI_SUCCESS;
   } else {
      return BOTAN_FFI_ERROR_BAD_PARAMETER;
   }
}

template <class ECPublicKey_t>
int pubkey_load_ec_sec1(std::unique_ptr<ECPublicKey_t>& key,
                        std::span<const uint8_t> sec1,
                        std::string_view curve_name) {
   if(!Botan::EC_Group::supports_named_group(curve_name)) {
      return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
   }

   const auto group = Botan::EC_Group::from_name(curve_name);

   if(auto pt = Botan::EC_AffinePoint::deserialize(group, sec1)) {
      key.reset(new ECPublicKey_t(group, pt.value()));
      return BOTAN_FFI_SUCCESS;
   } else {
      return BOTAN_FFI_ERROR_BAD_PARAMETER;
   }
}

#endif

Botan::BigInt pubkey_get_field(const Botan::Public_Key& key, std::string_view field) {
#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
   // Not currently handled by get_int_field
   if(const Botan::EC_PublicKey* ecc = dynamic_cast<const Botan::EC_PublicKey*>(&key)) {
      if(field == "public_x") {
         return Botan::BigInt::from_bytes(ecc->_public_ec_point().x_bytes());
      } else if(field == "public_y") {
         return Botan::BigInt::from_bytes(ecc->_public_ec_point().y_bytes());
      }
   }
#endif

   try {
      return key.get_int_field(field);
   } catch(Botan::Unknown_PK_Field_Name&) {
      throw Botan_FFI::FFI_Error("Unknown key field", BOTAN_FFI_ERROR_BAD_PARAMETER);
   }
}

Botan::BigInt privkey_get_field(const Botan::Private_Key& key, std::string_view field) {
#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
   // Not currently handled by get_int_field
   if(const Botan::EC_PublicKey* ecc = dynamic_cast<const Botan::EC_PublicKey*>(&key)) {
      if(field == "public_x") {
         return Botan::BigInt::from_bytes(ecc->_public_ec_point().x_bytes());
      } else if(field == "public_y") {
         return Botan::BigInt::from_bytes(ecc->_public_ec_point().y_bytes());
      }
   }
#endif

   try {
      return key.get_int_field(field);
   } catch(Botan::Unknown_PK_Field_Name&) {
      throw Botan_FFI::FFI_Error("Unknown key field", BOTAN_FFI_ERROR_BAD_PARAMETER);
   }
}

}  // namespace

extern "C" {

using namespace Botan_FFI;

int botan_pubkey_get_field(botan_mp_t output, botan_pubkey_t key, const char* field_name_cstr) {
   if(field_name_cstr == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   const std::string field_name(field_name_cstr);

   return BOTAN_FFI_VISIT(key, [=](const auto& k) { safe_get(output) = pubkey_get_field(k, field_name); });
}

int botan_privkey_get_field(botan_mp_t output, botan_privkey_t key, const char* field_name_cstr) {
   if(field_name_cstr == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   const std::string field_name(field_name_cstr);

   return BOTAN_FFI_VISIT(key, [=](const auto& k) { safe_get(output) = privkey_get_field(k, field_name); });
}

/* RSA specific operations */

int botan_privkey_create_rsa(botan_privkey_t* key_obj, botan_rng_t rng_obj, size_t n_bits) {
   if(n_bits < 1024 || n_bits > 16 * 1024) {
      return BOTAN_FFI_ERROR_BAD_PARAMETER;
   }

   const std::string n_str = std::to_string(n_bits);

   return botan_privkey_create(key_obj, "RSA", n_str.c_str(), rng_obj);
}

int botan_privkey_load_rsa(botan_privkey_t* key, botan_mp_t rsa_p, botan_mp_t rsa_q, botan_mp_t rsa_e) {
#if defined(BOTAN_HAS_RSA)
   if(key == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      auto rsa = std::make_unique<Botan::RSA_PrivateKey>(safe_get(rsa_p), safe_get(rsa_q), safe_get(rsa_e));
      return ffi_new_object(key, std::move(rsa));
   });
#else
   BOTAN_UNUSED(key, rsa_p, rsa_q, rsa_e);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_load_rsa_pkcs1(botan_privkey_t* key, const uint8_t bits[], size_t len) {
#if defined(BOTAN_HAS_RSA)
   if(Botan::any_null_pointers(key, bits)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::AlgorithmIdentifier alg_id("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM);
      auto rsa = std::make_unique<Botan::RSA_PrivateKey>(alg_id, std::span{bits, len});
      return ffi_new_object(key, std::move(rsa));
   });
#else
   BOTAN_UNUSED(key, bits, len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_rsa(botan_pubkey_t* key, botan_mp_t n, botan_mp_t e) {
#if defined(BOTAN_HAS_RSA)
   if(key == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      auto rsa = std::make_unique<Botan::RSA_PublicKey>(safe_get(n), safe_get(e));
      return ffi_new_object(key, std::move(rsa));
   });
#else
   BOTAN_UNUSED(key, n, e);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_rsa_pkcs1(botan_pubkey_t* key, const uint8_t bits[], size_t len) {
#if defined(BOTAN_HAS_RSA)
   if(Botan::any_null_pointers(key, bits)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::AlgorithmIdentifier alg_id("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM);
      auto rsa = std::make_unique<Botan::RSA_PublicKey>(alg_id, std::span{bits, len});
      return ffi_new_object(key, std::move(rsa));
   });
#else
   BOTAN_UNUSED(key, bits, len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_rsa_get_p(botan_mp_t p, botan_privkey_t key) {
   return botan_privkey_get_field(p, key, "p");
}

int botan_privkey_rsa_get_q(botan_mp_t q, botan_privkey_t key) {
   return botan_privkey_get_field(q, key, "q");
}

int botan_privkey_rsa_get_n(botan_mp_t n, botan_privkey_t key) {
   return botan_privkey_get_field(n, key, "n");
}

int botan_privkey_rsa_get_e(botan_mp_t e, botan_privkey_t key) {
   return botan_privkey_get_field(e, key, "e");
}

int botan_privkey_rsa_get_d(botan_mp_t d, botan_privkey_t key) {
   return botan_privkey_get_field(d, key, "d");
}

int botan_pubkey_rsa_get_e(botan_mp_t e, botan_pubkey_t key) {
   return botan_pubkey_get_field(e, key, "e");
}

int botan_pubkey_rsa_get_n(botan_mp_t n, botan_pubkey_t key) {
   return botan_pubkey_get_field(n, key, "n");
}

int botan_privkey_rsa_get_privkey(botan_privkey_t rsa_key, uint8_t out[], size_t* out_len, uint32_t flags) {
#if defined(BOTAN_HAS_RSA)
   return BOTAN_FFI_VISIT(rsa_key, [=](const auto& k) -> int {
      if(const Botan::RSA_PrivateKey* rsa = dynamic_cast<const Botan::RSA_PrivateKey*>(&k)) {
         if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_DER) {
            return write_vec_output(out, out_len, rsa->private_key_bits());
         } else if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_PEM) {
            // TODO define new generic functions for this
            return write_str_output(reinterpret_cast<char*>(out),
                                    out_len,
                                    Botan::PEM_Code::encode(rsa->private_key_bits(), "RSA PRIVATE KEY"));
         } else {
            return BOTAN_FFI_ERROR_BAD_FLAG;
         }
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(rsa_key, out, out_len, flags);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* DSA specific operations */
int botan_privkey_create_dsa(botan_privkey_t* key, botan_rng_t rng_obj, size_t pbits, size_t qbits) {
#if defined(BOTAN_HAS_DSA)

   if(Botan::any_null_pointers(rng_obj, key)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   if((pbits % 64 != 0) || (qbits % 8 != 0) || (pbits < 1024) || (pbits > 3072) || (qbits < 160) || (qbits > 256)) {
      return BOTAN_FFI_ERROR_BAD_PARAMETER;
   }

   return ffi_guard_thunk(__func__, [=]() -> int {
      Botan::RandomNumberGenerator& rng = safe_get(rng_obj);
      const Botan::DL_Group group(rng, Botan::DL_Group::Prime_Subgroup, pbits, qbits);
      auto dsa = std::make_unique<Botan::DSA_PrivateKey>(rng, group);
      return ffi_new_object(key, std::move(dsa));
   });
#else
   BOTAN_UNUSED(key, rng_obj, pbits, qbits);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_load_dsa(botan_privkey_t* key, botan_mp_t p, botan_mp_t q, botan_mp_t g, botan_mp_t x) {
#if defined(BOTAN_HAS_DSA)
   if(key == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::DL_Group group(safe_get(p), safe_get(q), safe_get(g));
      auto dsa = std::make_unique<Botan::DSA_PrivateKey>(group, safe_get(x));
      return ffi_new_object(key, std::move(dsa));
   });
#else
   BOTAN_UNUSED(key, p, q, g, x);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_dsa(botan_pubkey_t* key, botan_mp_t p, botan_mp_t q, botan_mp_t g, botan_mp_t y) {
#if defined(BOTAN_HAS_DSA)
   if(key == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::DL_Group group(safe_get(p), safe_get(q), safe_get(g));
      auto dsa = std::make_unique<Botan::DSA_PublicKey>(group, safe_get(y));
      return ffi_new_object(key, std::move(dsa));
   });
#else
   BOTAN_UNUSED(key, p, q, g, y);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_dsa_get_x(botan_mp_t x, botan_privkey_t key) {
   return botan_privkey_get_field(x, key, "x");
}

int botan_pubkey_dsa_get_p(botan_mp_t p, botan_pubkey_t key) {
   return botan_pubkey_get_field(p, key, "p");
}

int botan_pubkey_dsa_get_q(botan_mp_t q, botan_pubkey_t key) {
   return botan_pubkey_get_field(q, key, "q");
}

int botan_pubkey_dsa_get_g(botan_mp_t g, botan_pubkey_t key) {
   return botan_pubkey_get_field(g, key, "g");
}

int botan_pubkey_dsa_get_y(botan_mp_t y, botan_pubkey_t key) {
   return botan_pubkey_get_field(y, key, "y");
}

int botan_privkey_create_ecdsa(botan_privkey_t* key_obj, botan_rng_t rng_obj, const char* param_str) {
   return botan_privkey_create(key_obj, "ECDSA", param_str, rng_obj);
}

/* ECDSA specific operations */

int botan_pubkey_ecc_key_used_explicit_encoding(botan_pubkey_t key) {
#if defined(BOTAN_HAS_ECC_KEY)
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::Public_Key& pub_key = safe_get(key);
      const Botan::EC_PublicKey* ec_key = dynamic_cast<const Botan::EC_PublicKey*>(&pub_key);

      if(ec_key == nullptr) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      return ec_key->domain().used_explicit_encoding() ? 1 : 0;
   });
#else
   BOTAN_UNUSED(key);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

// NOLINTBEGIN(misc-misplaced-const)

int botan_pubkey_load_ecdsa(botan_pubkey_t* key,
                            const botan_mp_t public_x,
                            const botan_mp_t public_y,
                            const char* curve_name) {
#if defined(BOTAN_HAS_ECDSA)
   if(Botan::any_null_pointers(key, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::ECDSA_PublicKey> p_key;

      const int rc = pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name);
      if(rc == BOTAN_FFI_SUCCESS) {
         ffi_new_object(key, std::move(p_key));
      }

      return rc;
   });
#else
   BOTAN_UNUSED(key, public_x, public_y, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_ecdsa_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) {
#if defined(BOTAN_HAS_ECDSA)
   if(Botan::any_null_pointers(key, sec1, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::ECDSA_PublicKey> p_key;

      const int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name);
      if(rc == BOTAN_FFI_SUCCESS) {
         ffi_new_object(key, std::move(p_key));
      }

      return rc;
   });
#else
   BOTAN_UNUSED(key, sec1, sec1_len, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_load_ecdsa(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) {
#if defined(BOTAN_HAS_ECDSA)
   if(Botan::any_null_pointers(key, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::ECDSA_PrivateKey> p_key;
      const int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name);
      if(rc == BOTAN_FFI_SUCCESS) {
         ffi_new_object(key, std::move(p_key));
      }
      return rc;
   });
#else
   BOTAN_UNUSED(key, scalar, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* ElGamal specific operations */
int botan_privkey_create_elgamal(botan_privkey_t* key, botan_rng_t rng_obj, size_t pbits, size_t qbits) {
#if defined(BOTAN_HAS_ELGAMAL)
   if(Botan::any_null_pointers(key, rng_obj)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   if(pbits < 1024 || qbits < 160) {
      return BOTAN_FFI_ERROR_BAD_PARAMETER;
   }

   const Botan::DL_Group::PrimeType prime_type =
      ((pbits - 1) == qbits) ? Botan::DL_Group::Strong : Botan::DL_Group::Prime_Subgroup;

   return ffi_guard_thunk(__func__, [=]() -> int {
      Botan::RandomNumberGenerator& rng = safe_get(rng_obj);
      const Botan::DL_Group group(rng, prime_type, pbits, qbits);
      auto elg = std::make_unique<Botan::ElGamal_PrivateKey>(rng, group);
      return ffi_new_object(key, std::move(elg));
   });
#else
   BOTAN_UNUSED(key, rng_obj, pbits, qbits);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_elgamal(botan_pubkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t y) {
#if defined(BOTAN_HAS_ELGAMAL)
   if(key == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::DL_Group group(safe_get(p), safe_get(g));
      auto elg = std::make_unique<Botan::ElGamal_PublicKey>(group, safe_get(y));
      return ffi_new_object(key, std::move(elg));
   });
#else
   BOTAN_UNUSED(key, p, g, y);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_load_elgamal(botan_privkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t x) {
#if defined(BOTAN_HAS_ELGAMAL)
   if(key == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::DL_Group group(safe_get(p), safe_get(g));
      auto elg = std::make_unique<Botan::ElGamal_PrivateKey>(group, safe_get(x));
      return ffi_new_object(key, std::move(elg));
   });
#else
   BOTAN_UNUSED(key, p, g, x);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* Diffie Hellman specific operations */

int botan_privkey_create_dh(botan_privkey_t* key_obj, botan_rng_t rng_obj, const char* param_str) {
   return botan_privkey_create(key_obj, "DH", param_str, rng_obj);
}

int botan_privkey_load_dh(botan_privkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t x) {
#if defined(BOTAN_HAS_DIFFIE_HELLMAN)
   if(key == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::DL_Group group(safe_get(p), safe_get(g));
      auto dh = std::make_unique<Botan::DH_PrivateKey>(group, safe_get(x));
      return ffi_new_object(key, std::move(dh));
   });
#else
   BOTAN_UNUSED(key, p, g, x);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_dh(botan_pubkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t y) {
#if defined(BOTAN_HAS_DIFFIE_HELLMAN)
   if(key == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::DL_Group group(safe_get(p), safe_get(g));
      auto dh = std::make_unique<Botan::DH_PublicKey>(group, safe_get(y));
      return ffi_new_object(key, std::move(dh));
   });
#else
   BOTAN_UNUSED(key, p, g, y);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* ECDH + x25519/x448 specific operations */

int botan_privkey_create_ecdh(botan_privkey_t* key_obj, botan_rng_t rng_obj, const char* param_str) {
   if(Botan::any_null_pointers(key_obj, param_str)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key_obj = nullptr;

   const std::string params(param_str);

   if(params == "X25519" || params == "x25519" || params == "curve25519") {
      return botan_privkey_create(key_obj, "X25519", "", rng_obj);
   }

   if(params == "X448" || params == "x448") {
      return botan_privkey_create(key_obj, "X448", "", rng_obj);
   }

   return botan_privkey_create(key_obj, "ECDH", param_str, rng_obj);
}

int botan_pubkey_load_ecdh(botan_pubkey_t* key,
                           const botan_mp_t public_x,
                           const botan_mp_t public_y,
                           const char* curve_name) {
#if defined(BOTAN_HAS_ECDH)
   if(Botan::any_null_pointers(key, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::ECDH_PublicKey> p_key;
      const int rc = pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name);

      if(rc == BOTAN_FFI_SUCCESS) {
         ffi_new_object(key, std::move(p_key));
      }
      return rc;
   });
#else
   BOTAN_UNUSED(key, public_x, public_y, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_ecdh_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) {
#if defined(BOTAN_HAS_ECDH)
   if(Botan::any_null_pointers(key, sec1, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::ECDH_PublicKey> p_key;

      const int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name);
      if(rc == BOTAN_FFI_SUCCESS) {
         ffi_new_object(key, std::move(p_key));
      }

      return rc;
   });
#else
   BOTAN_UNUSED(key, sec1, sec1_len, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_load_ecdh(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) {
#if defined(BOTAN_HAS_ECDH)
   if(Botan::any_null_pointers(key, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::ECDH_PrivateKey> p_key;
      const int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name);
      if(rc == BOTAN_FFI_SUCCESS) {
         ffi_new_object(key, std::move(p_key));
      }
      return rc;
   });
#else
   BOTAN_UNUSED(key, scalar, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* SM2 specific operations */

int botan_pubkey_sm2_compute_za(
   uint8_t out[], size_t* out_len, const char* ident, const char* hash_algo, const botan_pubkey_t key) {
   if(Botan::any_null_pointers(out, out_len, ident, hash_algo, key)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

#if defined(BOTAN_HAS_SM2)
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::Public_Key& pub_key = safe_get(key);
      const Botan::EC_PublicKey* ec_key = dynamic_cast<const Botan::EC_PublicKey*>(&pub_key);

      if(ec_key == nullptr) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      if(ec_key->algo_name() != "SM2") {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      const std::string ident_str(ident);
      std::unique_ptr<Botan::HashFunction> hash = Botan::HashFunction::create_or_throw(hash_algo);

      const auto& pt = ec_key->_public_ec_point();

      const auto za = Botan::sm2_compute_za(*hash, ident_str, ec_key->domain(), pt);

      return write_vec_output(out, out_len, za);
   });
#else
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_sm2(botan_pubkey_t* key,
                          const botan_mp_t public_x,
                          const botan_mp_t public_y,
                          const char* curve_name) {
#if defined(BOTAN_HAS_SM2)
   if(Botan::any_null_pointers(key, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::SM2_PublicKey> p_key;
      if(pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name) == 0) {
         return ffi_new_object(key, std::move(p_key));
      } else {
         return BOTAN_FFI_ERROR_UNKNOWN_ERROR;
      }
   });
#else
   BOTAN_UNUSED(key, public_x, public_y, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_sm2_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) {
#if defined(BOTAN_HAS_SM2)
   if(Botan::any_null_pointers(key, sec1, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::SM2_PublicKey> p_key;

      const int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name);
      if(rc == BOTAN_FFI_SUCCESS) {
         ffi_new_object(key, std::move(p_key));
      }

      return rc;
   });
#else
   BOTAN_UNUSED(key, sec1, sec1_len, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_load_sm2(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) {
#if defined(BOTAN_HAS_SM2)
   if(Botan::any_null_pointers(key, curve_name)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      std::unique_ptr<Botan::SM2_PrivateKey> p_key;
      const int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name);

      if(rc == BOTAN_FFI_SUCCESS) {
         ffi_new_object(key, std::move(p_key));
      }
      return rc;
   });
#else
   BOTAN_UNUSED(key, scalar, curve_name);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_sm2_enc(botan_pubkey_t* key,
                              const botan_mp_t public_x,
                              const botan_mp_t public_y,
                              const char* curve_name) {
   return botan_pubkey_load_sm2(key, public_x, public_y, curve_name);
}

int botan_privkey_load_sm2_enc(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) {
   return botan_privkey_load_sm2(key, scalar, curve_name);
}

/* EC key specific operations */

int botan_ec_privkey_get_private_key(botan_privkey_t key, botan_ec_scalar_t* value) {
   if(Botan::any_null_pointers(value)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_ECC_KEY)
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::EC_PrivateKey* ec_key = dynamic_cast<const Botan::EC_PrivateKey*>(&safe_get(key));
      if(ec_key == nullptr) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
      return ffi_new_object(value, std::make_unique<Botan::EC_Scalar>(ec_key->_private_key()));
   });
#else
   BOTAN_UNUSED(key, value);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_ec_privkey_get_group(botan_privkey_t key, botan_ec_group_t* ec_group) {
   if(Botan::any_null_pointers(ec_group)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

#if defined(BOTAN_HAS_ECC_KEY)
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::EC_PrivateKey* ec_key = dynamic_cast<const Botan::EC_PrivateKey*>(&safe_get(key));
      if(ec_key == nullptr) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
      return ffi_new_object(ec_group, std::make_unique<Botan::EC_Group>(ec_key->domain()));
   });
#else
   BOTAN_UNUSED(key, ec_group);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_ec_pubkey_get_group(botan_pubkey_t key, botan_ec_group_t* ec_group) {
   if(Botan::any_null_pointers(ec_group)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_ECC_KEY)
   return ffi_guard_thunk(__func__, [=]() -> int {
      const Botan::EC_PublicKey* ec_key = dynamic_cast<const Botan::EC_PublicKey*>(&safe_get(key));
      if(ec_key == nullptr) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
      return ffi_new_object(ec_group, std::make_unique<Botan::EC_Group>(ec_key->domain()));
   });
#else
   BOTAN_UNUSED(key, ec_group);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* Ed25519 specific operations */

int botan_privkey_load_ed25519(botan_privkey_t* key, const uint8_t privkey[32]) {
#if defined(BOTAN_HAS_ED25519)
   if(any_null_pointers(key, privkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      auto ed25519 =
         std::make_unique<Botan::Ed25519_PrivateKey>(Botan::Ed25519_PrivateKey::from_seed(std::span{privkey, 32}));
      return ffi_new_object(key, std::move(ed25519));
   });
#else
   BOTAN_UNUSED(key, privkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_ed25519(botan_pubkey_t* key, const uint8_t pubkey[32]) {
#if defined(BOTAN_HAS_ED25519)
   if(any_null_pointers(key, pubkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      const std::vector<uint8_t> pubkey_vec(pubkey, pubkey + 32);
      auto ed25519 = std::make_unique<Botan::Ed25519_PublicKey>(pubkey_vec);
      return ffi_new_object(key, std::move(ed25519));
   });
#else
   BOTAN_UNUSED(key, pubkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_ed25519_get_privkey(botan_privkey_t key, uint8_t output[64]) {
   if(output == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_ED25519)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) {
      if(auto ed = dynamic_cast<const Botan::Ed25519_PrivateKey*>(&k)) {
         const auto ed_key = ed->raw_private_key_bits();
         if(ed_key.size() != 64) {
            return BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE;
         }
         Botan::copy_mem(output, ed_key.data(), ed_key.size());
         return BOTAN_FFI_SUCCESS;
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, output);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_ed25519_get_pubkey(botan_pubkey_t key, uint8_t output[32]) {
   if(output == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_ED25519)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) {
      if(auto ed = dynamic_cast<const Botan::Ed25519_PublicKey*>(&k)) {
         const std::vector<uint8_t>& ed_key = ed->get_public_key();
         if(ed_key.size() != 32) {
            return BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE;
         }
         Botan::copy_mem(output, ed_key.data(), ed_key.size());
         return BOTAN_FFI_SUCCESS;
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, output);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* Ed448 specific operations */

int botan_privkey_load_ed448(botan_privkey_t* key, const uint8_t privkey[57]) {
#if defined(BOTAN_HAS_ED448)
   if(any_null_pointers(key, privkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      auto ed448 = std::make_unique<Botan::Ed448_PrivateKey>(std::span(privkey, 57));
      return ffi_new_object(key, std::move(ed448));
   });
#else
   BOTAN_UNUSED(key, privkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_ed448(botan_pubkey_t* key, const uint8_t pubkey[57]) {
#if defined(BOTAN_HAS_ED448)
   if(any_null_pointers(key, pubkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      auto ed448 = std::make_unique<Botan::Ed448_PublicKey>(std::span(pubkey, 57));
      return ffi_new_object(key, std::move(ed448));
   });
#else
   BOTAN_UNUSED(key, pubkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_ed448_get_privkey(botan_privkey_t key, uint8_t output[57]) {
   if(output == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_ED448)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) {
      if(auto ed = dynamic_cast<const Botan::Ed448_PrivateKey*>(&k)) {
         const auto ed_key = ed->raw_private_key_bits();
         Botan::copy_mem(std::span(output, 57), ed_key);
         return BOTAN_FFI_SUCCESS;
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, output);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_ed448_get_pubkey(botan_pubkey_t key, uint8_t output[57]) {
   if(output == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_ED448)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) {
      if(auto ed = dynamic_cast<const Botan::Ed448_PublicKey*>(&k)) {
         const auto ed_key = ed->public_key_bits();
         Botan::copy_mem(std::span(output, 57), ed_key);
         return BOTAN_FFI_SUCCESS;
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, output);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* X25519 specific operations */

int botan_privkey_load_x25519(botan_privkey_t* key, const uint8_t privkey[32]) {
#if defined(BOTAN_HAS_X25519)
   if(any_null_pointers(key, privkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      auto x25519 = std::make_unique<Botan::X25519_PrivateKey>(std::span{privkey, 32});
      return ffi_new_object(key, std::move(x25519));
   });
#else
   BOTAN_UNUSED(key, privkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_x25519(botan_pubkey_t* key, const uint8_t pubkey[32]) {
#if defined(BOTAN_HAS_X25519)
   if(any_null_pointers(key, pubkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      auto x25519 = std::make_unique<Botan::X25519_PublicKey>(std::span{pubkey, 32});
      return ffi_new_object(key, std::move(x25519));
   });
#else
   BOTAN_UNUSED(key, pubkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_x25519_get_privkey(botan_privkey_t key, uint8_t output[32]) {
   if(output == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_X25519)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) {
      if(auto x25519 = dynamic_cast<const Botan::X25519_PrivateKey*>(&k)) {
         const auto x25519_key = x25519->raw_private_key_bits();
         if(x25519_key.size() != 32) {
            return BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE;
         }
         Botan::copy_mem(output, x25519_key.data(), x25519_key.size());
         return BOTAN_FFI_SUCCESS;
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, output);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_x25519_get_pubkey(botan_pubkey_t key, uint8_t output[32]) {
   if(output == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_X25519)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) {
      if(auto x25519 = dynamic_cast<const Botan::X25519_PublicKey*>(&k)) {
         Botan::copy_mem(std::span{output, 32}, x25519->raw_public_key_bits());
         return BOTAN_FFI_SUCCESS;
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, output);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/* X448 specific operations */

int botan_privkey_load_x448(botan_privkey_t* key, const uint8_t privkey[56]) {
#if defined(BOTAN_HAS_X448)
   if(any_null_pointers(key, privkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      auto x448 = std::make_unique<Botan::X448_PrivateKey>(std::span{privkey, 56});
      return ffi_new_object(key, std::move(x448));
   });
#else
   BOTAN_UNUSED(key, privkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_x448(botan_pubkey_t* key, const uint8_t pubkey[56]) {
#if defined(BOTAN_HAS_X448)
   if(any_null_pointers(key, pubkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;
   return ffi_guard_thunk(__func__, [=]() -> int {
      auto x448 = std::make_unique<Botan::X448_PublicKey>(std::span{pubkey, 56});
      return ffi_new_object(key, std::move(x448));
   });
#else
   BOTAN_UNUSED(key, pubkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_x448_get_privkey(botan_privkey_t key, uint8_t output[56]) {
   if(output == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_X448)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) {
      if(auto x448 = dynamic_cast<const Botan::X448_PrivateKey*>(&k)) {
         const auto x448_key = x448->raw_private_key_bits();
         Botan::copy_mem(std::span{output, 56}, x448_key);
         return BOTAN_FFI_SUCCESS;
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, output);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_x448_get_pubkey(botan_pubkey_t key, uint8_t output[56]) {
   if(output == nullptr) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
#if defined(BOTAN_HAS_X448)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) {
      if(auto x448 = dynamic_cast<const Botan::X448_PublicKey*>(&k)) {
         Botan::copy_mem(std::span{output, 56}, x448->raw_public_key_bits());
         return BOTAN_FFI_SUCCESS;
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, output);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/*
* Algorithm specific key operations: Kyber
*/

int botan_privkey_load_kyber(botan_privkey_t* key, const uint8_t privkey[], size_t key_len) {
#if defined(BOTAN_HAS_KYBER)
   if(Botan::any_null_pointers(key, privkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   const auto mode = [](size_t len) -> std::optional<Botan::KyberMode> {
      if(len == 1632) {
         return Botan::KyberMode::Kyber512_R3;
      } else if(len == 2400) {
         return Botan::KyberMode::Kyber768_R3;
      } else if(len == 3168) {
         return Botan::KyberMode::Kyber1024_R3;
      } else {
         return {};
      }
   }(key_len);

   if(mode.has_value()) {
      return ffi_guard_thunk(__func__, [=]() -> int {
         auto kyber = std::make_unique<Botan::Kyber_PrivateKey>(std::span{privkey, key_len}, *mode);
         return ffi_new_object(key, std::move(kyber));
      });
   } else {
      return BOTAN_FFI_ERROR_BAD_PARAMETER;
   }
#else
   BOTAN_UNUSED(key, key_len, privkey);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_kyber(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len) {
#if defined(BOTAN_HAS_KYBER)
   if(Botan::any_null_pointers(key, pubkey)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }
   *key = nullptr;

   const auto mode = [](size_t len) -> std::optional<Botan::KyberMode> {
      if(len == 800) {
         return Botan::KyberMode::Kyber512_R3;
      } else if(len == 1184) {
         return Botan::KyberMode::Kyber768_R3;
      } else if(len == 1568) {
         return Botan::KyberMode::Kyber1024_R3;
      } else {
         return {};
      }
   }(key_len);

   if(mode.has_value()) {
      return ffi_guard_thunk(__func__, [=]() -> int {
         auto kyber = std::make_unique<Botan::Kyber_PublicKey>(std::span{pubkey, key_len}, *mode);
         return ffi_new_object(key, std::move(kyber));
      });
   } else {
      return BOTAN_FFI_ERROR_BAD_PARAMETER;
   }
#else
   BOTAN_UNUSED(key, pubkey, key_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_privkey_view_kyber_raw_key(botan_privkey_t key, botan_view_ctx ctx, botan_view_bin_fn view) {
#if defined(BOTAN_HAS_KYBER)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) -> int {
      if(auto kyber = dynamic_cast<const Botan::Kyber_PrivateKey*>(&k)) {
         return invoke_view_callback(view, ctx, kyber->raw_private_key_bits());
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, ctx, view);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_view_kyber_raw_key(botan_pubkey_t key, botan_view_ctx ctx, botan_view_bin_fn view) {
#if defined(BOTAN_HAS_KYBER)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) -> int {
      if(auto kyber = dynamic_cast<const Botan::Kyber_PublicKey*>(&k)) {
         return invoke_view_callback(view, ctx, kyber->public_key_bits());
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, ctx, view);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/*
* Algorithm specific key operations: ML-KEM
*/

int botan_privkey_load_ml_kem(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* mlkem_mode) {
#if defined(BOTAN_HAS_ML_KEM)
   if(Botan::any_null_pointers(key, privkey, mlkem_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      auto mode = Botan::ML_KEM_Mode(mlkem_mode);
      if(!mode.is_ml_kem()) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      auto mlkem_key = std::make_unique<Botan::ML_KEM_PrivateKey>(std::span{privkey, key_len}, mode);
      return ffi_new_object(key, std::move(mlkem_key));
   });
#else
   BOTAN_UNUSED(key, key_len, privkey, mlkem_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_ml_kem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* mlkem_mode) {
#if defined(BOTAN_HAS_ML_KEM)
   if(Botan::any_null_pointers(key, pubkey, mlkem_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      auto mode = Botan::ML_KEM_Mode(mlkem_mode);
      if(!mode.is_ml_kem()) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      auto mlkem_key = std::make_unique<Botan::ML_KEM_PublicKey>(std::span{pubkey, key_len}, mode.mode());
      return ffi_new_object(key, std::move(mlkem_key));
   });
#else
   BOTAN_UNUSED(key, key_len, pubkey, mlkem_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/*
* Algorithm specific key operations: ML-DSA
*/

int botan_privkey_load_ml_dsa(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* mldsa_mode) {
#if defined(BOTAN_HAS_ML_DSA)
   if(Botan::any_null_pointers(key, privkey, mldsa_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      auto mode = Botan::ML_DSA_Mode(mldsa_mode);
      if(!mode.is_ml_dsa()) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      auto mldsa_key = std::make_unique<Botan::ML_DSA_PrivateKey>(std::span{privkey, key_len}, mode);
      return ffi_new_object(key, std::move(mldsa_key));
   });
#else
   BOTAN_UNUSED(key, key_len, privkey, mldsa_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_ml_dsa(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* mldsa_mode) {
#if defined(BOTAN_HAS_ML_DSA)
   if(Botan::any_null_pointers(key, pubkey, mldsa_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      auto mode = Botan::ML_DSA_Mode(mldsa_mode);
      if(!mode.is_ml_dsa()) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      auto mldsa_key = std::make_unique<Botan::ML_DSA_PublicKey>(std::span{pubkey, key_len}, mode);
      return ffi_new_object(key, std::move(mldsa_key));
   });
#else
   BOTAN_UNUSED(key, key_len, pubkey, mldsa_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/*
* Algorithm specific key operations: SLH-DSA
*/

int botan_privkey_load_slh_dsa(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* slhdsa_mode) {
#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE)
   if(Botan::any_null_pointers(key, privkey, slhdsa_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      auto mode = Botan::SLH_DSA_Parameters::create(slhdsa_mode);
      if(!mode.is_slh_dsa()) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      auto slhdsa_key = std::make_unique<Botan::SLH_DSA_PrivateKey>(std::span{privkey, key_len}, mode);
      return ffi_new_object(key, std::move(slhdsa_key));
   });
#else
   BOTAN_UNUSED(key, key_len, privkey, slhdsa_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_slh_dsa(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* slhdsa_mode) {
#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE)
   if(Botan::any_null_pointers(key, pubkey, slhdsa_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      auto mode = Botan::SLH_DSA_Parameters::create(slhdsa_mode);
      if(!mode.is_slh_dsa()) {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }

      auto mldsa_key = std::make_unique<Botan::SLH_DSA_PublicKey>(std::span{pubkey, key_len}, mode);
      return ffi_new_object(key, std::move(mldsa_key));
   });
#else
   BOTAN_UNUSED(key, key_len, pubkey, slhdsa_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/*
* Algorithm specific key operations: FrodoKEM
*/

int botan_privkey_load_frodokem(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* frodo_mode) {
#if defined(BOTAN_HAS_FRODOKEM)
   if(Botan::any_null_pointers(key, privkey, frodo_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      const auto mode = Botan::FrodoKEMMode(frodo_mode);
      auto frodo_key = std::make_unique<Botan::FrodoKEM_PrivateKey>(std::span{privkey, key_len}, mode);
      return ffi_new_object(key, std::move(frodo_key));
   });
#else
   BOTAN_UNUSED(key, privkey, key_len, frodo_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_frodokem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* frodo_mode) {
#if defined(BOTAN_HAS_FRODOKEM)
   if(Botan::any_null_pointers(key, pubkey, frodo_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      const auto mode = Botan::FrodoKEMMode(frodo_mode);
      auto frodo_key = std::make_unique<Botan::FrodoKEM_PublicKey>(std::span{pubkey, key_len}, mode);
      return ffi_new_object(key, std::move(frodo_key));
   });
#else
   BOTAN_UNUSED(key, pubkey, key_len, frodo_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/*
* Algorithm specific key operations : Classic McEliece
*/

int botan_privkey_load_classic_mceliece(botan_privkey_t* key,
                                        const uint8_t privkey[],
                                        size_t key_len,
                                        const char* cmce_mode) {
#if defined(BOTAN_HAS_CLASSICMCELIECE)
   if(Botan::any_null_pointers(key, privkey, cmce_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      const auto mode = Botan::Classic_McEliece_Parameter_Set::from_string(cmce_mode);
      auto cmce_key = std::make_unique<Botan::Classic_McEliece_PrivateKey>(std::span{privkey, key_len}, mode);
      return ffi_new_object(key, std::move(cmce_key));
   });
#else
   BOTAN_UNUSED(key, privkey, key_len, cmce_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_classic_mceliece(botan_pubkey_t* key,
                                       const uint8_t pubkey[],
                                       size_t key_len,
                                       const char* cmce_mode) {
#if defined(BOTAN_HAS_CLASSICMCELIECE)
   if(Botan::any_null_pointers(key, pubkey, cmce_mode)) {
      return BOTAN_FFI_ERROR_NULL_POINTER;
   }

   *key = nullptr;

   return ffi_guard_thunk(__func__, [=]() -> int {
      const auto mode = Botan::Classic_McEliece_Parameter_Set::from_string(cmce_mode);
      auto cmce_key = std::make_unique<Botan::Classic_McEliece_PublicKey>(std::span{pubkey, key_len}, mode);
      return ffi_new_object(key, std::move(cmce_key));
   });
#else
   BOTAN_UNUSED(key, pubkey, key_len, cmce_mode);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_view_ec_public_point(const botan_pubkey_t key, botan_view_ctx ctx, botan_view_bin_fn view) {
#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
   return BOTAN_FFI_VISIT(key, [=](const auto& k) -> int {
      if(auto ecc = dynamic_cast<const Botan::EC_PublicKey*>(&k)) {
         auto pt = ecc->_public_ec_point().serialize_uncompressed();
         return invoke_view_callback(view, ctx, pt);
      } else {
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
      }
   });
#else
   BOTAN_UNUSED(key, view, ctx);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

// NOLINTEND(misc-misplaced-const)

int botan_privkey_create_mceliece(botan_privkey_t* key_obj, botan_rng_t rng_obj, size_t n, size_t t) {
   const std::string mce_params = std::to_string(n) + "," + std::to_string(t);
   return botan_privkey_create(key_obj, "McEliece", mce_params.c_str(), rng_obj);
}

int botan_mceies_decrypt(botan_privkey_t mce_key_obj,
                         const char* aead,
                         const uint8_t ct[],
                         size_t ct_len,
                         const uint8_t ad[],
                         size_t ad_len,
                         uint8_t out[],
                         size_t* out_len) {
   BOTAN_UNUSED(mce_key_obj, aead, ct, ct_len, ad, ad_len, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
}

int botan_mceies_encrypt(botan_pubkey_t mce_key_obj,
                         botan_rng_t rng_obj,
                         const char* aead,
                         const uint8_t pt[],
                         size_t pt_len,
                         const uint8_t ad[],
                         size_t ad_len,
                         uint8_t out[],
                         size_t* out_len) {
   BOTAN_UNUSED(mce_key_obj, rng_obj, aead, pt, pt_len, ad, ad_len, out, out_len);
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
}
}
