//    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-2022 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/dco/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,
            const CryptoAlgs::KeyDerivation key_method,
            CryptoDCFactory &dc_factory_delegate,
            const Receiver::Ptr &rcv_arg,
            const Frame::Ptr &frame_arg)
        : CryptoDCContext(key_method),
          rcv(rcv_arg),
          dc_context_delegate(dc_factory_delegate.new_obj(cipher, digest, key_method)),
          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,
                                         const CryptoAlgs::KeyDerivation key_method) override
    {
        return new Context(cipher, digest, key_method, *dc_factory_delegate, rcv, frame);
    }

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

} // namespace KoRekey
} // namespace openvpn

#endif
