/*
* (C) 2017 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/tls_algos.h>

#include <botan/exceptn.h>
#include <botan/internal/fmt.h>

#include <algorithm>
#include <array>

namespace Botan::TLS {

std::string kdf_algo_to_string(KDF_Algo algo) {
   switch(algo) {
      case KDF_Algo::SHA_1:
         return "SHA-1";
      case KDF_Algo::SHA_256:
         return "SHA-256";
      case KDF_Algo::SHA_384:
         return "SHA-384";
   }

   throw Invalid_State("kdf_algo_to_string unknown enum value");
}

std::string kex_method_to_string(Kex_Algo method) {
   switch(method) {
      case Kex_Algo::STATIC_RSA:
         return "RSA";
      case Kex_Algo::DH:
         return "DH";
      case Kex_Algo::ECDH:
         return "ECDH";
      case Kex_Algo::PSK:
         return "PSK";
      case Kex_Algo::ECDHE_PSK:
         return "ECDHE_PSK";
      case Kex_Algo::DHE_PSK:
         return "DHE_PSK";
      case Kex_Algo::KEM:
         return "KEM";
      case Kex_Algo::KEM_PSK:
         return "KEM_PSK";
      case Kex_Algo::HYBRID:
         return "HYBRID";
      case Kex_Algo::HYBRID_PSK:
         return "HYBRID_PSK";
      case Kex_Algo::UNDEFINED:
         return "UNDEFINED";
   }

   throw Invalid_State("kex_method_to_string unknown enum value");
}

Kex_Algo kex_method_from_string(std::string_view str) {
   if(str == "RSA") {
      return Kex_Algo::STATIC_RSA;
   }

   if(str == "DH") {
      return Kex_Algo::DH;
   }

   if(str == "ECDH") {
      return Kex_Algo::ECDH;
   }

   if(str == "PSK") {
      return Kex_Algo::PSK;
   }

   if(str == "ECDHE_PSK") {
      return Kex_Algo::ECDHE_PSK;
   }

   if(str == "DHE_PSK") {
      return Kex_Algo::DHE_PSK;
   }

   if(str == "KEM") {
      return Kex_Algo::KEM;
   }

   if(str == "KEM_PSK") {
      return Kex_Algo::KEM_PSK;
   }

   if(str == "HYBRID") {
      return Kex_Algo::HYBRID;
   }

   if(str == "HYBRID_PSK") {
      return Kex_Algo::HYBRID_PSK;
   }

   if(str == "UNDEFINED") {
      return Kex_Algo::UNDEFINED;
   }

   throw Invalid_Argument(fmt("Unknown kex method '{}'", str));
}

std::string auth_method_to_string(Auth_Method method) {
   switch(method) {
      case Auth_Method::RSA:
         return "RSA";
      case Auth_Method::ECDSA:
         return "ECDSA";
      case Auth_Method::IMPLICIT:
         return "IMPLICIT";
      case Auth_Method::UNDEFINED:
         return "UNDEFINED";
   }

   throw Invalid_State("auth_method_to_string unknown enum value");
}

Auth_Method auth_method_from_string(std::string_view str) {
   if(str == "RSA") {
      return Auth_Method::RSA;
   }
   if(str == "ECDSA") {
      return Auth_Method::ECDSA;
   }
   if(str == "IMPLICIT") {
      return Auth_Method::IMPLICIT;
   }
   if(str == "UNDEFINED") {
      return Auth_Method::UNDEFINED;
   }

   throw Invalid_Argument(fmt("Unknown TLS signature method '{}'", str));
}

namespace {

consteval auto available_group_params() {
   auto codes = std::array {
#if defined(BOTAN_HAS_PCURVES_SECP256R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
      Group_Params_Code::SECP256R1,
#endif

#if defined(BOTAN_HAS_PCURVES_SECP384R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::SECP384R1,
#endif

#if defined(BOTAN_HAS_PCURVES_SECP521R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::SECP521R1,
#endif

#if defined(BOTAN_HAS_PCURVES_BRAINPOOL256R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::BRAINPOOL256R1, Group_Params_Code::BRAINPOOL256R1TLS13,
#endif

#if defined(BOTAN_HAS_PCURVES_BRAINPOOL384R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::BRAINPOOL384R1, Group_Params_Code::BRAINPOOL384R1TLS13,
#endif

#if defined(BOTAN_HAS_PCURVES_BRAINPOOL512R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::BRAINPOOL512R1, Group_Params_Code::BRAINPOOL512R1TLS13,
#endif

#if defined(BOTAN_HAS_X25519)
         Group_Params_Code::X25519,
#endif

#if defined(BOTAN_HAS_X448)
         Group_Params_Code::X448,
#endif

#if defined(BOTAN_HAS_DIFFIE_HELLMAN)
         Group_Params_Code::FFDHE_2048, Group_Params_Code::FFDHE_3072, Group_Params_Code::FFDHE_4096,
         Group_Params_Code::FFDHE_6144, Group_Params_Code::FFDHE_8192,
#endif

#if defined(BOTAN_HAS_ML_KEM)
         Group_Params_Code::ML_KEM_512, Group_Params_Code::ML_KEM_768, Group_Params_Code::ML_KEM_1024,

   #if defined(BOTAN_HAS_PCURVES_SECP256R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::HYBRID_SECP256R1_ML_KEM_768,
   #endif

   #if defined(BOTAN_HAS_PCURVES_SECP384R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::HYBRID_SECP384R1_ML_KEM_1024,
   #endif

   #if defined(BOTAN_HAS_X25519)
         Group_Params_Code::HYBRID_X25519_ML_KEM_768,
   #endif
#endif

#if defined(BOTAN_HAS_FRODOKEM)
         Group_Params_Code::eFRODOKEM_640_SHAKE_OQS, Group_Params_Code::eFRODOKEM_976_SHAKE_OQS,
         Group_Params_Code::eFRODOKEM_1344_SHAKE_OQS, Group_Params_Code::eFRODOKEM_640_AES_OQS,
         Group_Params_Code::eFRODOKEM_976_AES_OQS, Group_Params_Code::eFRODOKEM_1344_AES_OQS,

   #if defined(BOTAN_HAS_PCURVES_SECP256R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS,
         Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS,
   #endif

   #if defined(BOTAN_HAS_PCURVES_SECP384R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS,
         Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS,
   #endif

   #if defined(BOTAN_HAS_PCURVES_SECP521R1) || defined(BOTAN_HAS_PCURVES_GENERIC)
         Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS,
         Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS,
   #endif

   #if defined(BOTAN_HAS_X25519)
         Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS,
         Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_AES_OQS,
   #endif

   #if defined(BOTAN_HAS_X448)
         Group_Params_Code::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS, Group_Params_Code::HYBRID_X448_eFRODOKEM_976_AES_OQS,
   #endif
#endif
   };

   std::sort(codes.begin(), codes.end());

   return codes;
}

}  // namespace

bool Group_Params::is_available() const {
   // For group codes we recognize, check the build-time availability table.
   // Unknown codes may be user-supplied custom groups handled via callbacks.
   if(to_string().has_value()) {
      static constexpr auto codes = available_group_params();
      return std::binary_search(codes.begin(), codes.end(), this->code());
   }
   return true;
}

std::optional<Group_Params_Code> Group_Params::pqc_hybrid_ecc() const {
   switch(m_code) {
      case Group_Params_Code::HYBRID_X25519_ML_KEM_768:

      case Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS:
      case Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_AES_OQS:
         return Group_Params_Code::X25519;

      case Group_Params_Code::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS:
      case Group_Params_Code::HYBRID_X448_eFRODOKEM_976_AES_OQS:
         return Group_Params_Code::X448;

      case Group_Params_Code::HYBRID_SECP256R1_ML_KEM_768:
      case Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS:
      case Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS:
         return Group_Params_Code::SECP256R1;

      case Group_Params_Code::HYBRID_SECP384R1_ML_KEM_1024:
      case Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS:
      case Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS:
         return Group_Params_Code::SECP384R1;

      case Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS:
      case Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS:
         return Group_Params_Code::SECP521R1;

      default:
         return {};
   }
}

std::optional<Group_Params> Group_Params::from_string(std::string_view group_name) {
   if(group_name == "secp256r1") {
      return Group_Params::SECP256R1;
   }
   if(group_name == "secp384r1") {
      return Group_Params::SECP384R1;
   }
   if(group_name == "secp521r1") {
      return Group_Params::SECP521R1;
   }
   if(group_name == "brainpool256r1") {
      return Group_Params::BRAINPOOL256R1;
   }
   if(group_name == "brainpool384r1") {
      return Group_Params::BRAINPOOL384R1;
   }
   if(group_name == "brainpool512r1") {
      return Group_Params::BRAINPOOL512R1;
   }
   if(group_name == "x25519") {
      return Group_Params::X25519;
   }
   if(group_name == "x448") {
      return Group_Params::X448;
   }
   if(group_name == "brainpool256r1tls13") {
      return Group_Params::BRAINPOOL256R1TLS13;
   }
   if(group_name == "brainpool384r1tls13") {
      return Group_Params::BRAINPOOL384R1TLS13;
   }
   if(group_name == "brainpool512r1tls13") {
      return Group_Params::BRAINPOOL512R1TLS13;
   }

   if(group_name == "ffdhe/ietf/2048") {
      return Group_Params::FFDHE_2048;
   }
   if(group_name == "ffdhe/ietf/3072") {
      return Group_Params::FFDHE_3072;
   }
   if(group_name == "ffdhe/ietf/4096") {
      return Group_Params::FFDHE_4096;
   }
   if(group_name == "ffdhe/ietf/6144") {
      return Group_Params::FFDHE_6144;
   }
   if(group_name == "ffdhe/ietf/8192") {
      return Group_Params::FFDHE_8192;
   }

   if(group_name == "ML-KEM-512") {
      return Group_Params::ML_KEM_512;
   }
   if(group_name == "ML-KEM-768") {
      return Group_Params::ML_KEM_768;
   }
   if(group_name == "ML-KEM-1024") {
      return Group_Params::ML_KEM_1024;
   }

   if(group_name == "eFrodoKEM-640-SHAKE") {
      return Group_Params::eFRODOKEM_640_SHAKE_OQS;
   }
   if(group_name == "eFrodoKEM-976-SHAKE") {
      return Group_Params::eFRODOKEM_976_SHAKE_OQS;
   }
   if(group_name == "eFrodoKEM-1344-SHAKE") {
      return Group_Params::eFRODOKEM_1344_SHAKE_OQS;
   }
   if(group_name == "eFrodoKEM-640-AES") {
      return Group_Params::eFRODOKEM_640_AES_OQS;
   }
   if(group_name == "eFrodoKEM-976-AES") {
      return Group_Params::eFRODOKEM_976_AES_OQS;
   }
   if(group_name == "eFrodoKEM-1344-AES") {
      return Group_Params::eFRODOKEM_1344_AES_OQS;
   }

   if(group_name == "x25519/ML-KEM-768") {
      return Group_Params::HYBRID_X25519_ML_KEM_768;
   }
   if(group_name == "secp256r1/ML-KEM-768") {
      return Group_Params::HYBRID_SECP256R1_ML_KEM_768;
   }
   if(group_name == "secp384r1/ML-KEM-1024") {
      return Group_Params::HYBRID_SECP384R1_ML_KEM_1024;
   }

   if(group_name == "x25519/eFrodoKEM-640-SHAKE") {
      return Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS;
   }
   if(group_name == "x25519/eFrodoKEM-640-AES") {
      return Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS;
   }
   if(group_name == "x448/eFrodoKEM-976-SHAKE") {
      return Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS;
   }
   if(group_name == "x448/eFrodoKEM-976-AES") {
      return Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS;
   }

   if(group_name == "secp256r1/eFrodoKEM-640-SHAKE") {
      return Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS;
   }
   if(group_name == "secp256r1/eFrodoKEM-640-AES") {
      return Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS;
   }

   if(group_name == "secp384r1/eFrodoKEM-976-SHAKE") {
      return Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS;
   }
   if(group_name == "secp384r1/eFrodoKEM-976-AES") {
      return Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS;
   }

   if(group_name == "secp521r1/eFrodoKEM-1344-SHAKE") {
      return Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS;
   }
   if(group_name == "secp521r1/eFrodoKEM-1344-AES") {
      return Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS;
   }

   return std::nullopt;
}

std::optional<std::string> Group_Params::to_string() const {
   switch(m_code) {
      case Group_Params::SECP256R1:
         return "secp256r1";
      case Group_Params::SECP384R1:
         return "secp384r1";
      case Group_Params::SECP521R1:
         return "secp521r1";
      case Group_Params::BRAINPOOL256R1:
         return "brainpool256r1";
      case Group_Params::BRAINPOOL384R1:
         return "brainpool384r1";
      case Group_Params::BRAINPOOL512R1:
         return "brainpool512r1";
      case Group_Params::X25519:
         return "x25519";
      case Group_Params::X448:
         return "x448";
      case Group_Params::BRAINPOOL256R1TLS13:
         return "brainpool256r1tls13";
      case Group_Params::BRAINPOOL384R1TLS13:
         return "brainpool384r1tls13";
      case Group_Params::BRAINPOOL512R1TLS13:
         return "brainpool512r1tls13";

      case Group_Params::FFDHE_2048:
         return "ffdhe/ietf/2048";
      case Group_Params::FFDHE_3072:
         return "ffdhe/ietf/3072";
      case Group_Params::FFDHE_4096:
         return "ffdhe/ietf/4096";
      case Group_Params::FFDHE_6144:
         return "ffdhe/ietf/6144";
      case Group_Params::FFDHE_8192:
         return "ffdhe/ietf/8192";

      case Group_Params::ML_KEM_512:
         return "ML-KEM-512";
      case Group_Params::ML_KEM_768:
         return "ML-KEM-768";
      case Group_Params::ML_KEM_1024:
         return "ML-KEM-1024";

      case Group_Params::eFRODOKEM_640_SHAKE_OQS:
         return "eFrodoKEM-640-SHAKE";
      case Group_Params::eFRODOKEM_976_SHAKE_OQS:
         return "eFrodoKEM-976-SHAKE";
      case Group_Params::eFRODOKEM_1344_SHAKE_OQS:
         return "eFrodoKEM-1344-SHAKE";
      case Group_Params::eFRODOKEM_640_AES_OQS:
         return "eFrodoKEM-640-AES";
      case Group_Params::eFRODOKEM_976_AES_OQS:
         return "eFrodoKEM-976-AES";
      case Group_Params::eFRODOKEM_1344_AES_OQS:
         return "eFrodoKEM-1344-AES";

      case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS:
         return "x25519/eFrodoKEM-640-SHAKE";
      case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS:
         return "x25519/eFrodoKEM-640-AES";
      case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS:
         return "x448/eFrodoKEM-976-SHAKE";
      case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS:
         return "x448/eFrodoKEM-976-AES";
      case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS:
         return "secp256r1/eFrodoKEM-640-SHAKE";
      case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS:
         return "secp256r1/eFrodoKEM-640-AES";
      case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS:
         return "secp384r1/eFrodoKEM-976-SHAKE";
      case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS:
         return "secp384r1/eFrodoKEM-976-AES";
      case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS:
         return "secp521r1/eFrodoKEM-1344-SHAKE";
      case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS:
         return "secp521r1/eFrodoKEM-1344-AES";

      case Group_Params::HYBRID_X25519_ML_KEM_768:
         return "x25519/ML-KEM-768";
      case Group_Params::HYBRID_SECP256R1_ML_KEM_768:
         return "secp256r1/ML-KEM-768";
      case Group_Params::HYBRID_SECP384R1_ML_KEM_1024:
         return "secp384r1/ML-KEM-1024";

      default:
         return std::nullopt;
   }
}

std::optional<std::string> Group_Params::to_algorithm_spec() const {
   switch(m_code) {
      // Brainpool curves have two sets of code points. See RFCs 7027 and 8734.
      case Group_Params::BRAINPOOL256R1:
      case Group_Params::BRAINPOOL256R1TLS13:
         return "brainpool256r1";
      case Group_Params::BRAINPOOL384R1:
      case Group_Params::BRAINPOOL384R1TLS13:
         return "brainpool384r1";
      case Group_Params::BRAINPOOL512R1:
      case Group_Params::BRAINPOOL512R1TLS13:
         return "brainpool512r1";

      default:
         return to_string();
   }
}

std::string certificate_type_to_string(Certificate_Type type) {
   switch(type) {
      case Certificate_Type::X509:
         return "X509";
      case Certificate_Type::RawPublicKey:
         return "RawPublicKey";
   }

   return "Unknown";
}

Certificate_Type certificate_type_from_string(const std::string& type_str) {
   if(type_str == "X509") {
      return Certificate_Type::X509;
   } else if(type_str == "RawPublicKey") {
      return Certificate_Type::RawPublicKey;
   } else {
      throw Decoding_Error("Unknown certificate type: " + type_str);
   }
}

}  // namespace Botan::TLS
