/*
* TPM 2 interface
* (C) 2024 Jack Lloyd
* (C) 2024 René Meusel, Amos Treiber - Rohde & Schwarz Cybersecurity GmbH, financed by LANCOM Systems GmbH
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#ifndef BOTAN_TPM2_CONTEXT_H_
#define BOTAN_TPM2_CONTEXT_H_

#include <botan/exceptn.h>
#include <botan/rng.h>

#include <memory>
#include <optional>
#include <vector>

/// Forward declaration of TSS2 type for convenience
using TPM2_HANDLE = uint32_t;

/// Forward declaration of TSS2 type for convenience
using ESYS_TR = uint32_t;

struct ESYS_CONTEXT;

namespace Botan::TPM2 {

struct CryptoCallbackState;

class PrivateKey;
class SessionBundle;

/**
 * Central class for interacting with a TPM2. Additional to managing the
 * connection to the TPM, this provides authoritative information about the TPM's
 * capabilities. Also, it allows to persist and evict keys generated by the TPM.
 */
class BOTAN_PUBLIC_API(3, 6) Context final : public std::enable_shared_from_this<Context> {
   public:
      /**
       * @param tcti_nameconf  this is passed to Tss2_TctiLdr_Initialize verbatim
       */
      static std::shared_ptr<Context> create(const std::string& tcti_nameconf);

      /**
       * @param tcti  if set this is passed to Tss2_TctiLdr_Initialize_Ex verbatim
       *              otherwise a nullptr is passed.
       * @param conf  if set this is passed to Tss2_TctiLdr_Initialize_Ex verbatim
       *              otherwise a nullptr is passed.
       */
      static std::shared_ptr<Context> create(std::optional<std::string> tcti = {},
                                             std::optional<std::string> conf = {});

      /**
       * Create a TPM2::Context from an externally sourced TPM2-TSS ESYS
       * Context. Note that the input contexts need to remain alive for the
       * lifetime of the entire TPM2::Context! This allows to use Botan's TPM2
       * functionality within an existing ESAPI application.
       *
       * Note that Botan won't finalize an externally provided ESYS context,
       * this responsibility remains with the caller in this case.
       *
       * @param ctx  the already set up ESYS_CONTEXT*
       */
      static std::shared_ptr<Context> create(ESYS_CONTEXT* ctx);

      Context(const Context&) = delete;
      Context(Context&&) noexcept;
      ~Context();

      Context& operator=(const Context&) = delete;
      Context& operator=(Context&&) noexcept;

      /**
       * Overrides the TSS2's crypto callbacks with Botan's functionality.
       *
       * This replaces all cryptographic functionality required for the
       * communication with the TPM by botan's implementations. The TSS2
       * would otherwise use OpenSSL or mbedTLS.
       *
       * Note that the provided @p rng should not be dependent on the TPM.
       *
       * @param rng  the RNG to use for the crypto operations
       * @throws Not_Implemented if the TPM2-TSS does not support crypto callbacks
       * @sa supports_botan_crypto_backend()
       */
      void use_botan_crypto_backend(const std::shared_ptr<Botan::RandomNumberGenerator>& rng);

      /**
       * Checks if the TSS2 supports registering Botan's crypto backend at runtime.
       * Older versions of the TSS2 do not support this feature ( 4.0.0), also
       * Botan may be compiled without support for TSS' crypto backend.
       * @return true if the TSS2 supports Botan's crypto backend
       */
      static bool supports_botan_crypto_backend() noexcept;

      /// @returns true if botan is used for the TSS' crypto functions
      bool uses_botan_crypto_backend() const noexcept;

      /// @return an ESYS_CONTEXT* for use in other TPM2 functions.
      ESYS_CONTEXT* esys_context() noexcept;

      // NOLINTNEXTLINE(*-explicit-conversions) Intentional: enables transparent ESYS_CONTEXT* wrapper usage
      operator ESYS_CONTEXT*() noexcept { return esys_context(); }

      /// @return the Vendor of the TPM2
      std::string vendor() const;

      /// @returns the Manufacturer of the TPM2
      std::string manufacturer() const;

      /**
       * The @p algo_name can be any of the string algorithm specifiers used
       * elsewhere. For example, "RSA", "AES-128", "SHA-1", "CTR(3DES)", etc.
       *
       * @returns true if the specified algorithm is supported by the TPM
       */
      bool supports_algorithm(std::string_view algo_name) const;

      /// @returns the maximum number of random bytes to be requested at once
      size_t max_random_bytes_per_request() const;

      std::vector<ESYS_TR> transient_handles() const;

      /// @returns a persistent handle that is currently not in use
      ///          or std::nullopt if no such handle is available
      std::optional<TPM2_HANDLE> find_free_persistent_handle() const;

      std::vector<TPM2_HANDLE> persistent_handles() const;

      /// Makes @p key persistent at location @p persistent_handle or any free
      TPM2_HANDLE persist(TPM2::PrivateKey& key,
                          const SessionBundle& sessions,
                          std::span<const uint8_t> auth_value = {},
                          std::optional<TPM2_HANDLE> persistent_handle = std::nullopt);

      /// Evicts a persistent @p key from the TPM. The key cannot be used after.
      void evict(std::unique_ptr<TPM2::PrivateKey> key, const SessionBundle& sessions);

      // TODO: Currently this assumes that the SRK is a persistent object,
      //       this assumption may not hold forever.
      std::unique_ptr<TPM2::PrivateKey> storage_root_key(std::span<const uint8_t> auth_value,
                                                         const SessionBundle& sessions);

   private:
      Context(ESYS_CONTEXT* ctx, bool external);

#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
      friend void enable_crypto_callbacks(const std::shared_ptr<Context>&);
      CryptoCallbackState& crypto_callback_state();
#endif

   private:
      struct Impl;  // PImpl to avoid TPM2-TSS includes in this header
      std::unique_ptr<Impl> m_impl;
};

}  // namespace Botan::TPM2

#endif
