//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2018 OpenVPN Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

// OpenVPN 3 wrapper for kovpn crypto

#ifndef OPENVPN_KOVPN_KOREKEY_H
#define OPENVPN_KOVPN_KOREKEY_H

#include <openvpn/kovpn/kocrypto.hpp>

namespace openvpn {
  namespace KoRekey {

    class Receiver : public virtual RC<thread_unsafe_refcount>
    {
    public:
      typedef RCPtr<Receiver> Ptr;

      virtual void rekey(const CryptoDCInstance::RekeyType type,
			 const Info& info) = 0;

      virtual void explicit_exit_notify() {}
    };

    class Instance : public CryptoDCInstance
    {
    public:
      Instance(const Receiver::Ptr& rcv_arg,
	       const CryptoDCContext::Ptr& dc_context_delegate,
	       const unsigned int key_id,
	       const Frame::Ptr& frame)
	: rcv(rcv_arg),
	  info(dc_context_delegate, key_id, frame)
      {
      }

      // Initialization

      virtual unsigned int defined() const override
      {
	return CIPHER_DEFINED|HMAC_DEFINED|EXPLICIT_EXIT_NOTIFY_DEFINED;
      }

      virtual void init_cipher(StaticKey&& encrypt_key,
			       StaticKey&& decrypt_key) override
      {
	info.encrypt_cipher = std::move(encrypt_key);
	info.decrypt_cipher = std::move(decrypt_key);
      }

      virtual void init_hmac(StaticKey&& encrypt_key,
			     StaticKey&& decrypt_key) override
      {
	info.encrypt_hmac = std::move(encrypt_key);
	info.decrypt_hmac = std::move(decrypt_key);
      }

      virtual void init_pid(const int send_form,
			    const int recv_mode,
			    const int recv_form,
			    const char *recv_name,
			    const int recv_unit,
			    const SessionStats::Ptr& recv_stats_arg) override
      {
	info.tcp_linear = (recv_mode == PacketIDReceive::TCP_MODE);
      }

      virtual void init_remote_peer_id(const int remote_peer_id) override
      {
	info.remote_peer_id = remote_peer_id;
      }

      virtual bool consider_compression(const CompressContext& comp_ctx) override
      {
	info.comp_ctx = comp_ctx;
	return false;
      }

      // Rekeying

      virtual void rekey(const RekeyType type) override
      {
	rcv->rekey(type, info);
      }

      virtual void explicit_exit_notify() override
      {
	rcv->explicit_exit_notify();
      }

      // Encrypt/Decrypt -- data channel handled by kernel, so these methods
      // should never be reached.

      // returns true if packet ID is close to wrapping
      virtual bool encrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32) override
      {
	throw korekey_error("encrypt");
      }

      virtual Error::Type decrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32) override
      {
	throw korekey_error("decrypt");
      }

    private:
      Receiver::Ptr rcv;
      Info info;
    };

    class Context : public CryptoDCContext
    {
    public:
      Context(const CryptoAlgs::Type cipher,
	      const CryptoAlgs::Type digest,
	      CryptoDCFactory& dc_factory_delegate,
	      const Receiver::Ptr& rcv_arg,
	      const Frame::Ptr& frame_arg)
	: rcv(rcv_arg),
	  dc_context_delegate(dc_factory_delegate.new_obj(cipher, digest)),
	  frame(frame_arg)
      {
	Key::validate(cipher, digest);
      }

      virtual CryptoDCInstance::Ptr new_obj(const unsigned int key_id) override
      {
	return new Instance(rcv, dc_context_delegate, key_id, frame);
      }

      // Info for ProtoContext::options_string

      virtual Info crypto_info() override
      {
	return dc_context_delegate->crypto_info();
      }

      // Info for ProtoContext::link_mtu_adjust

      virtual size_t encap_overhead() const override
      {
	return dc_context_delegate->encap_overhead();
      }

    private:
      Receiver::Ptr rcv;
      CryptoDCContext::Ptr dc_context_delegate;
      Frame::Ptr frame;
    };

    class Factory : public CryptoDCFactory
    {
    public:
      Factory(const CryptoDCFactory::Ptr& dc_factory_delegate_arg,
	      const Receiver::Ptr& rcv_arg,
	      const Frame::Ptr& frame_arg)
	: dc_factory_delegate(dc_factory_delegate_arg),
	  rcv(rcv_arg),
	  frame(frame_arg)
      {
      }

      virtual CryptoDCContext::Ptr new_obj(const CryptoAlgs::Type cipher,
					   const CryptoAlgs::Type digest) override
      {
	return new Context(cipher, digest, *dc_factory_delegate, rcv, frame);
      }

    private:
      CryptoDCFactory::Ptr dc_factory_delegate;
      Receiver::Ptr rcv;
      Frame::Ptr frame;
    };

  }
}

#endif
