//    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-2017 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 CBC/HMAC data channel

#ifndef OPENVPN_CRYPTO_CRYPTO_CHM_H
#define OPENVPN_CRYPTO_CRYPTO_CHM_H

#include <openvpn/crypto/encrypt_chm.hpp>
#include <openvpn/crypto/decrypt_chm.hpp>
#include <openvpn/crypto/cryptodc.hpp>
#include <openvpn/random/randapi.hpp>

namespace openvpn {

  template <typename CRYPTO_API>
  class CryptoCHM : public CryptoDCInstance
  {
  public:
    typedef CryptoDCInstance Base;

    CryptoCHM(const CryptoAlgs::Type cipher_arg,
	      const CryptoAlgs::Type digest_arg,
	      const Frame::Ptr& frame_arg,
	      const SessionStats::Ptr& stats_arg,
	      const RandomAPI::Ptr& prng_arg)
      : cipher(cipher_arg),
	digest(digest_arg),
	frame(frame_arg),
	stats(stats_arg),
	prng(prng_arg)
    {
      encrypt_.frame = frame;
      decrypt_.frame = frame;
      encrypt_.set_prng(prng);
    }

    // Encrypt/Decrypt

    /* returns true if packet ID is close to wrapping */
    virtual bool encrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32)
    {
      encrypt_.encrypt(buf, now);
      return encrypt_.pid_send.wrap_warning();
    }

    virtual Error::Type decrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32)
    {
      return decrypt_.decrypt(buf, now);
    }

    // Initialization

    virtual void init_cipher(StaticKey&& encrypt_key,
			     StaticKey&& decrypt_key)
    {
      encrypt_.cipher.init(cipher, encrypt_key, CRYPTO_API::CipherContext::ENCRYPT);
      decrypt_.cipher.init(cipher, decrypt_key, CRYPTO_API::CipherContext::DECRYPT);
    }

    virtual void init_hmac(StaticKey&& encrypt_key,
			   StaticKey&& decrypt_key)
    {
      encrypt_.hmac.init(digest, encrypt_key);
      decrypt_.hmac.init(digest, 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)
    {
      encrypt_.pid_send.init(send_form);
      decrypt_.pid_recv.init(recv_mode, recv_form, recv_name, recv_unit, recv_stats_arg);
    }

    virtual bool consider_compression(const CompressContext& comp_ctx)
    {
      return true;
    }

    // Indicate whether or not cipher/digest is defined

    virtual unsigned int defined() const
    {
      unsigned int ret = CRYPTO_DEFINED;
      if (CryptoAlgs::defined(cipher))
	ret |= CIPHER_DEFINED;
      if (CryptoAlgs::defined(digest))
	ret |= HMAC_DEFINED;
      return ret;
    }

    // Rekeying

    virtual void rekey(const typename Base::RekeyType type)
    {
    }

  private:
    CryptoAlgs::Type cipher;
    CryptoAlgs::Type digest;
    Frame::Ptr frame;
    SessionStats::Ptr stats;
    RandomAPI::Ptr prng;

    EncryptCHM<CRYPTO_API> encrypt_;
    DecryptCHM<CRYPTO_API> decrypt_;
  };

  template <typename CRYPTO_API>
  class CryptoContextCHM : public CryptoDCContext
  {
  public:
    typedef RCPtr<CryptoContextCHM> Ptr;

    CryptoContextCHM(const CryptoAlgs::Type cipher_arg,
		     const CryptoAlgs::Type digest_arg,
		     const Frame::Ptr& frame_arg,
		     const SessionStats::Ptr& stats_arg,
		     const RandomAPI::Ptr& prng_arg)
      : cipher(CryptoAlgs::legal_dc_cipher(cipher_arg)),
	digest(CryptoAlgs::legal_dc_digest(digest_arg)),
	frame(frame_arg),
	stats(stats_arg),
	prng(prng_arg)
    {
    }

    virtual CryptoDCInstance::Ptr new_obj(const unsigned int key_id)
    {
      return new CryptoCHM<CRYPTO_API>(cipher, digest, frame, stats, prng);
    }

    // cipher/HMAC/key info
    virtual Info crypto_info()
    {
      Info ret;
      ret.cipher_alg = cipher;
      ret.hmac_alg = digest;
      return ret;
    }

    // Info for ProtoContext::link_mtu_adjust

    virtual size_t encap_overhead() const
    {
      return CryptoAlgs::size(digest) +       // HMAC
	CryptoAlgs::iv_length(cipher) +       // Cipher IV
	CryptoAlgs::block_size(cipher);       // worst-case PKCS#7 padding expansion
    }

  private:
    CryptoAlgs::Type cipher;
    CryptoAlgs::Type digest;
    Frame::Ptr frame;
    SessionStats::Ptr stats;
    RandomAPI::Ptr prng;
  };
}

#endif
