/*

Copyright (c) 2020, Paul-Louis Ageneau
Copyright (c) 2018, Alexandre Janniaux
Copyright (c) 2020-2022, Arvid Norberg
Copyright (c) 2020, Steven Siloti
Copyright (c) 2021, Alden Torres
All rights reserved.

You may use, distribute and modify this code under the terms of the BSD license,
see LICENSE file.
*/

#ifndef TORRENT_SSL_HPP_INCLUDED
#define TORRENT_SSL_HPP_INCLUDED

#include "libtorrent/config.hpp"
#include "libtorrent/string_view.hpp"
#include "libtorrent/aux_/export.hpp"

#if TORRENT_USE_SSL

#include "libtorrent/aux_/disable_warnings_push.hpp"

#include <boost/asio.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp>

#ifndef BOOST_NO_EXCEPTIONS
#include <boost/system/system_error.hpp>
#endif

#ifdef TORRENT_USE_OPENSSL
#include <openssl/opensslv.h> // for OPENSSL_VERSION_NUMBER
#if OPENSSL_VERSION_NUMBER < 0x1010100fL
#error OpenSSL too old, libtorrent requires least OpenSSL 1.1.1 or later
#endif
#ifdef TORRENT_WINDOWS
// because openssl includes winsock.h, we must include winsock2.h first
#include <winsock2.h>
#endif
#include <openssl/ssl.h>
#include <openssl/crypto.h>
#include <openssl/rand.h>
#include <boost/asio/ssl.hpp>
#endif

#ifdef TORRENT_USE_GNUTLS
#include <gnutls/gnutls.h>
#include <boost/asio/gnutls.hpp>
#endif

#include "libtorrent/aux_/disable_warnings_pop.hpp"

#ifdef TORRENT_BUILD_SIMULATOR
#include "simulator/simulator.hpp"
#endif

#include <string>
#include <cstddef>
#include <functional>
#include <exception>

namespace libtorrent::aux::ssl {

using error_code = boost::system::error_code;

#if defined TORRENT_USE_OPENSSL
#if defined TORRENT_BUILD_SIMULATOR
	using sim::asio::ssl::context;
	using sim::asio::ssl::stream_base;
	using sim::asio::ssl::stream;
#else
	using boost::asio::ssl::context;
	using boost::asio::ssl::stream_base;
	using boost::asio::ssl::stream;
#endif
using boost::asio::ssl::verify_context;
#if BOOST_VERSION >= 107300
using boost::asio::ssl::host_name_verification;
#else
using host_name_verification = boost::asio::ssl::rfc2818_verification;
#endif

using native_context_type = SSL_CTX*;
using native_stream_type = SSL*;
using context_handle_type = native_context_type;
using stream_handle_type = native_stream_type;

typedef int (*server_name_callback_type)(SSL* s, int*, void* arg);

// restrict negotiation to TLS 1.2 and later, disabling the deprecated and
// insecure SSLv2, SSLv3, TLS 1.0 and TLS 1.1 protocol versions
constexpr context::options no_legacy_tls_versions =
	context::no_sslv2 | context::no_sslv3 | context::no_tlsv1 | context::no_tlsv1_1;

#elif defined TORRENT_USE_GNUTLS
using boost::asio::gnutls::context;
using boost::asio::gnutls::stream_base;
using boost::asio::gnutls::stream;
using boost::asio::gnutls::verify_context;
using boost::asio::gnutls::host_name_verification;

using native_context_type = context::native_handle_type;
using native_stream_type = stream_base::native_handle_type;
using context_handle_type = context*;
using stream_handle_type = stream_base*;

typedef bool (*server_name_callback_type)(stream_handle_type handle, std::string const& name, void* arg);

// GnuTLS only exposes options to disable SSLv2 and SSLv3, since it never
// negotiates anything older than TLS 1.0 to begin with
constexpr context::options no_legacy_tls_versions = context::no_sslv2 | context::no_sslv3;

#endif

namespace error {

#if defined TORRENT_USE_OPENSSL
using boost::asio::error::get_ssl_category;
using boost::asio::ssl::error::get_stream_category;
#elif defined TORRENT_USE_GNUTLS
using boost::asio::gnutls::error::get_ssl_category;
using boost::asio::gnutls::error::get_stream_category;
#endif

}

inline context_handle_type get_handle(context &c)
{
#if defined TORRENT_USE_OPENSSL
	return c.native_handle();
#elif defined TORRENT_USE_GNUTLS
	return &c;
#endif
}

template<typename T>
stream_handle_type get_handle(stream<T>& s)
{
#if defined TORRENT_USE_OPENSSL
	return s.native_handle();
#elif defined TORRENT_USE_GNUTLS
	return &s;
#endif
}

template<typename T>
context_handle_type get_context_handle(stream<T>& s)
{
#if defined TORRENT_USE_OPENSSL
	return SSL_get_SSL_CTX(s.native_handle());
#elif defined TORRENT_USE_GNUTLS
	return &s.get_context();
#endif
}

TORRENT_EXTRA_EXPORT void set_trust_certificate(native_context_type nc, string_view pem, error_code &ec);

TORRENT_EXTRA_EXPORT void set_server_name_callback(context_handle_type c, server_name_callback_type cb, void* arg, error_code& ec);
TORRENT_EXTRA_EXPORT void set_host_name(stream_handle_type s, std::string const& name, error_code& ec);

TORRENT_EXTRA_EXPORT void set_context(stream_handle_type s, context_handle_type c);
TORRENT_EXTRA_EXPORT bool has_context(stream_handle_type s, context_handle_type c);
TORRENT_EXTRA_EXPORT context_handle_type get_context(stream_handle_type s);

} // libtorrent::aux:ssl

#endif // TORRENT_USE_SSL

#endif
