//    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/>.

// ProtoContext, the fundamental OpenVPN protocol implementation.
// It can be used by OpenVPN clients, servers, or unit tests.

#ifndef OPENVPN_SSL_PROTO_H
#define OPENVPN_SSL_PROTO_H

#include <cstring>
#include <string>
#include <sstream>
#include <algorithm>                  // for std::min
#include <cstdint>                    // for std::uint32_t, etc.
#include <memory>

#include <openvpn/common/exception.hpp>
#include <openvpn/common/size.hpp>
#include <openvpn/common/version.hpp>
#include <openvpn/common/platform_name.hpp>
#include <openvpn/common/rc.hpp>
#include <openvpn/common/hexstr.hpp>
#include <openvpn/common/options.hpp>
#include <openvpn/common/mode.hpp>
#include <openvpn/common/socktypes.hpp>
#include <openvpn/common/number.hpp>
#include <openvpn/common/likely.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/common/to_string.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/buffer/safestr.hpp>
#include <openvpn/buffer/bufcomposed.hpp>
#include <openvpn/ip/ip4.hpp>
#include <openvpn/ip/ip6.hpp>
#include <openvpn/ip/udp.hpp>
#include <openvpn/ip/tcp.hpp>
#include <openvpn/time/time.hpp>
#include <openvpn/time/durhelper.hpp>
#include <openvpn/frame/frame.hpp>
#include <openvpn/random/randapi.hpp>
#include <openvpn/crypto/cryptoalgs.hpp>
#include <openvpn/crypto/cryptodc.hpp>
#include <openvpn/crypto/cipher.hpp>
#include <openvpn/crypto/ovpnhmac.hpp>
#include <openvpn/crypto/tls_crypt.hpp>
#include <openvpn/crypto/tls_crypt_v2.hpp>
#include <openvpn/crypto/packet_id.hpp>
#include <openvpn/crypto/static_key.hpp>
#include <openvpn/crypto/bs64_data_limit.hpp>
#include <openvpn/log/sessionstats.hpp>
#include <openvpn/ssl/protostack.hpp>
#include <openvpn/ssl/psid.hpp>
#include <openvpn/ssl/tlsprf.hpp>
#include <openvpn/ssl/datalimit.hpp>
#include <openvpn/ssl/mssparms.hpp>
#include <openvpn/transport/mssfix.hpp>
#include <openvpn/transport/protocol.hpp>
#include <openvpn/tun/layer.hpp>
#include <openvpn/tun/tunmtu.hpp>
#include <openvpn/compress/compress.hpp>
#include <openvpn/ssl/proto_context_options.hpp>
#include <openvpn/ssl/peerinfo.hpp>
#include <openvpn/ssl/ssllog.hpp>

#if OPENVPN_DEBUG_PROTO >= 1
#define OPENVPN_LOG_PROTO(x) OPENVPN_LOG(x)
#define OPENVPN_LOG_STRING_PROTO(x) OPENVPN_LOG_STRING(x)
#else
#define OPENVPN_LOG_PROTO(x)
#define OPENVPN_LOG_STRING_PROTO(x)
#endif

#if OPENVPN_DEBUG_PROTO >= 2
#define OPENVPN_LOG_PROTO_VERBOSE(x) OPENVPN_LOG(x)
#else
#define OPENVPN_LOG_PROTO_VERBOSE(x)
#endif

/*

ProtoContext -- OpenVPN protocol implementation

Protocol negotiation states:

Client:

1. send client reset to server
2. wait for server reset from server AND ack from 1 (C_WAIT_RESET, C_WAIT_RESET_ACK)
3. start SSL handshake
4. send auth message to server
5. wait for server auth message AND ack from 4 (C_WAIT_AUTH, C_WAIT_AUTH_ACK)
6. go active (ACTIVE)

Server:

1. wait for client reset (S_WAIT_RESET)
2. send server reset to client
3. wait for ACK from 2 (S_WAIT_RESET_ACK)
4. start SSL handshake
5. wait for auth message from client (S_WAIT_AUTH)
6. send auth message to client
7. wait for ACK from 6 (S_WAIT_AUTH_ACK)
8. go active (ACTIVE)

*/

namespace openvpn {

  // utility namespace for ProtoContext
  namespace proto_context_private {
    namespace {
      const unsigned char auth_prefix[] = { 0, 0, 0, 0, 2 }; // CONST GLOBAL

      const unsigned char keepalive_message[] = {    // CONST GLOBAL
	0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
	0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
      };

      enum {
	KEEPALIVE_FIRST_BYTE = 0x2a  // first byte of keepalive message
      };

      inline bool is_keepalive(const Buffer& buf)
      {
	return buf.size() >= sizeof(keepalive_message)
	  && buf[0] == KEEPALIVE_FIRST_BYTE
	  && !std::memcmp(keepalive_message, buf.c_data(), sizeof(keepalive_message));
      }

      const unsigned char explicit_exit_notify_message[] = {    // CONST GLOBAL
	0x28, 0x7f, 0x34, 0x6b, 0xd4, 0xef, 0x7a, 0x81,
	0x2d, 0x56, 0xb8, 0xd3, 0xaf, 0xc5, 0x45, 0x9c,
	6 // OCC_EXIT
      };

      enum {
	EXPLICIT_EXIT_NOTIFY_FIRST_BYTE = 0x28  // first byte of exit message
      };
    }
  }

  class ProtoContext
  {
  protected:
    static constexpr size_t APP_MSG_MAX = 65536;

    enum {
      // packet opcode (high 5 bits) and key-id (low 3 bits) are combined in one byte
      KEY_ID_MASK =             0x07,
      OPCODE_SHIFT =            3,

      // packet opcodes -- the V1 is intended to allow protocol changes in the future
      //CONTROL_HARD_RESET_CLIENT_V1 = 1,   // (obsolete) initial key from client, forget previous state
      //CONTROL_HARD_RESET_SERVER_V1 = 2,   // (obsolete) initial key from server, forget previous state
      CONTROL_SOFT_RESET_V1 =        3,   // new key, graceful transition from old to new key
      CONTROL_V1 =                   4,   // control channel packet (usually TLS ciphertext)
      ACK_V1 =                       5,   // acknowledgement for packets received
      DATA_V1 =                      6,   // data channel packet with 1-byte header
      DATA_V2 =                      9,   // data channel packet with 4-byte header

      // indicates key_method >= 2
      CONTROL_HARD_RESET_CLIENT_V2 = 7,   // initial key from client, forget previous state
      CONTROL_HARD_RESET_CLIENT_V3 = 10,  // initial key from client, forget previous state
      CONTROL_HARD_RESET_SERVER_V2 = 8,   // initial key from server, forget previous state

      // define the range of legal opcodes
      FIRST_OPCODE =                3,
      LAST_OPCODE =                 9,
      INVALID_OPCODE =              0,

      // DATA_V2 constants
      OP_SIZE_V2 = 4,                // size of initial packet opcode
      OP_PEER_ID_UNDEF = 0x00FFFFFF, // indicates that Peer ID is undefined

      // states
      // C_x : client states
      // S_x : server states

      // ACK states -- must be first before other states
      STATE_UNDEF=-1,
      C_WAIT_RESET_ACK=0,
      C_WAIT_AUTH_ACK=1,
      S_WAIT_RESET_ACK=2,
      S_WAIT_AUTH_ACK=3,
      LAST_ACK_STATE=3, // all ACK states must be <= this value

      // key negotiation states (client)
      C_INITIAL=4,
      C_WAIT_RESET=5,   // must be C_INITIAL+1
      C_WAIT_AUTH=6,

      // key negotiation states (server)
      S_INITIAL=7,
      S_WAIT_RESET=8,   // must be S_INITIAL+1
      S_WAIT_AUTH=9,

      // key negotiation states (client and server)
      ACTIVE=10,
    };

    static unsigned int opcode_extract(const unsigned int op)
    {
      return op >> OPCODE_SHIFT;
    }

    static unsigned int key_id_extract(const unsigned int op)
    {
      return op & KEY_ID_MASK;
    }

    static size_t op_head_size(const unsigned int op)
    {
      return opcode_extract(op) == DATA_V2 ? OP_SIZE_V2 : 1;
    }

    static unsigned int op_compose(const unsigned int opcode, const unsigned int key_id)
    {
      return (opcode << OPCODE_SHIFT) | key_id;
    }

    static unsigned int op32_compose(const unsigned int opcode,
				     const unsigned int key_id,
				     const int op_peer_id)
    {
      return (op_compose(opcode, key_id) << 24) | (op_peer_id & 0x00FFFFFF);
    }

  public:
    OPENVPN_EXCEPTION(proto_error);
    OPENVPN_EXCEPTION(process_server_push_error);
    OPENVPN_EXCEPTION_INHERIT(option_error, proto_option_error);

    // configuration data passed to ProtoContext constructor
    class Config : public RCCopyable<thread_unsafe_refcount>
    {
    public:
      typedef RCPtr<Config> Ptr;

      // master SSL context factory
      SSLFactoryAPI::Ptr ssl_factory;

      // data channel
      CryptoDCSettings dc;

      // TLSPRF factory
      TLSPRFFactory::Ptr tlsprf_factory;

      // master Frame object
      Frame::Ptr frame;

      // (non-smart) pointer to current time
      TimePtr now;

      // Random number generator.
      // Use-cases demand highest cryptographic strength
      // such as key generation.
      RandomAPI::Ptr rng;

      // Pseudo-random number generator.
      // Use-cases demand cryptographic strength
      // combined with high performance.  Used for
      // IV and ProtoSessionID generation.
      RandomAPI::Ptr prng;

      // If relay mode is enabled, connect to a special OpenVPN
      // server that acts as a relay/proxy to a second server.
      bool relay_mode = false;

      // defer data channel initialization until after client options pull
      bool dc_deferred = false;

      // transmit username/password creds to server (client-only)
      bool xmit_creds = true;

      // Transport protocol, i.e. UDPv4, etc.
      Protocol protocol; // set with set_protocol()

      // OSI layer
      Layer layer;

      // compressor
      CompressContext comp_ctx;

      // tls_auth/crypt parms
      OpenVPNStaticKey tls_key; // leave this undefined to disable tls_auth/crypt
      bool tls_crypt_v2 = false; // needed to distinguish between tls-crypt and tls-crypt-v2 server mode
      BufferAllocated wkc; // leave this undefined to disable tls-crypt-v2 on client

      OvpnHMACFactory::Ptr tls_auth_factory;
      OvpnHMACContext::Ptr tls_auth_context;
      int key_direction = -1;        // 0, 1, or -1 for bidirectional

      TLSCryptFactory::Ptr tls_crypt_factory;
      TLSCryptContext::Ptr tls_crypt_context;

      TLSCryptMetadataFactory::Ptr tls_crypt_metadata_factory;

      // reliability layer parms
      reliable::id_t reliable_window = 0;
      size_t max_ack_list = 0;

      // packet_id parms for both data and control channels
      int pid_mode = 0;                // PacketIDReceive::UDP_MODE or PacketIDReceive::TCP_MODE

      // timeout parameters, relative to construction of KeyContext object
      Time::Duration handshake_window; // SSL/TLS negotiation must complete by this time
      Time::Duration become_primary;   // KeyContext (that is ACTIVE) becomes primary at this time
      Time::Duration renegotiate;      // start SSL/TLS renegotiation at this time
      Time::Duration expire;           // KeyContext expires at this time
      Time::Duration tls_timeout;      // Packet retransmit timeout on TLS control channel

      // keepalive parameters
      Time::Duration keepalive_ping;
      Time::Duration keepalive_timeout;

      // extra peer info key/value pairs generated by client app
      PeerInfo::Set::Ptr extra_peer_info;

      // op header
      bool enable_op32 = false;
      int remote_peer_id = -1; // -1 to disable
      int local_peer_id = -1;  // -1 to disable

      // MTU
      unsigned int tun_mtu = 1500;
      MSSParms mss_parms;
      unsigned int mss_inter = 0;

      // Debugging
      int debug_level = 1;

      // Compatibility
      bool force_aes_cbc_ciphersuites = false;

      // For compatibility with openvpn2 we send initial options on rekeying,
      // instead of possible modifications caused by NCP
      std::string initial_options;

      void load(const OptionList& opt, const ProtoContextOptions& pco,
		const int default_key_direction, const bool server)
      {
	// first set defaults
	reliable_window = 4;
	max_ack_list = 4;
	handshake_window = Time::Duration::seconds(60);
	renegotiate = Time::Duration::seconds(3600);
	tls_timeout = Time::Duration::seconds(1);
	keepalive_ping = Time::Duration::seconds(8);
	keepalive_timeout = Time::Duration::seconds(40);
	comp_ctx = CompressContext(CompressContext::NONE, false);
	protocol = Protocol();
	pid_mode = PacketIDReceive::UDP_MODE;
	key_direction = default_key_direction;

	// layer
	{
	  const Option* dev = opt.get_ptr("dev-type");
	  if (!dev)
	    dev = opt.get_ptr("dev");
	  if (!dev)
	    throw proto_option_error("missing dev-type or dev option");
	  const std::string& dev_type = dev->get(1, 64);
	  if (string::starts_with(dev_type, "tun"))
	    layer = Layer(Layer::OSI_LAYER_3);
	  else if (string::starts_with(dev_type, "tap"))
	    throw proto_option_error("TAP mode is not supported");
	  else
	    throw proto_option_error("bad dev-type");
	}

	// cipher/digest/tls-auth/tls-crypt
	{
	  CryptoAlgs::Type cipher = CryptoAlgs::NONE;
	  CryptoAlgs::Type digest = CryptoAlgs::NONE;

	  // data channel cipher
	  {
	    const Option *o = opt.get_ptr("cipher");
	    if (o)
	      {
		const std::string& cipher_name = o->get(1, 128);
		if (cipher_name != "none")
		  cipher = CryptoAlgs::lookup(cipher_name);
	      }
	    else
	      cipher = CryptoAlgs::lookup("BF-CBC");
	  }

	  // data channel HMAC
	  {
	    const Option *o = opt.get_ptr("auth");
	    if (o)
	      {
		const std::string& auth_name = o->get(1, 128);
		if (auth_name != "none")
		  digest = CryptoAlgs::lookup(auth_name);
	      }
	    else
	      digest = CryptoAlgs::lookup("SHA1");
	  }
	  dc.set_cipher(cipher);
	  dc.set_digest(digest);

	  // tls-auth
	  {
	    const Option *o = opt.get_ptr(relay_prefix("tls-auth"));
	    if (o)
	      {
		if (tls_crypt_context)
		  throw proto_option_error("tls-auth and tls-crypt are mutually exclusive");

		tls_key.parse(o->get(1, 0));

		const Option *tad = opt.get_ptr(relay_prefix("tls-auth-digest"));
		if (tad)
		  digest = CryptoAlgs::lookup(tad->get(1, 128));
		if (digest != CryptoAlgs::NONE)
		  set_tls_auth_digest(digest);
	      }
	  }

	  // tls-crypt
	  {
	    const Option *o = opt.get_ptr(relay_prefix("tls-crypt"));
	    if (o)
	      {
		if (tls_auth_context)
		  throw proto_option_error("tls-auth and tls-crypt are mutually exclusive");
		if (tls_crypt_context)
		  throw proto_option_error("tls-crypt and tls-crypt-v2 are mutually exclusive");

		tls_key.parse(o->get(1, 0));

		digest = CryptoAlgs::lookup("SHA256");
		cipher = CryptoAlgs::lookup("AES-256-CTR");

		if ((digest == CryptoAlgs::NONE) || (cipher == CryptoAlgs::NONE))
		  throw proto_option_error("missing support for tls-crypt algorithms");

		set_tls_crypt_algs(digest, cipher);
	      }
	  }

	  // tls-crypt-v2
	  {
	    const Option *o = opt.get_ptr(relay_prefix("tls-crypt-v2"));
	    if (o)
	      {
		if (tls_auth_context)
		  throw proto_option_error("tls-auth and tls-crypt-v2 are mutually exclusive");
		if (tls_crypt_context)
		  throw proto_option_error("tls-crypt and tls-crypt-v2 are mutually exclusive");

		digest = CryptoAlgs::lookup("SHA256");
		cipher = CryptoAlgs::lookup("AES-256-CTR");

		if ((digest == CryptoAlgs::NONE) || (cipher == CryptoAlgs::NONE))
		  throw proto_option_error("missing support for tls-crypt-v2 algorithms");

		// initialize tls_crypt_context
		set_tls_crypt_algs(digest, cipher);

		std::string keyfile = o->get(1, 0);

		if (opt.exists("client"))
		  {
		    // in client mode expect the key to be a PEM encoded tls-crypt-v2 client key (key + WKc)
		    TLSCryptV2ClientKey tls_crypt_v2_key(tls_crypt_context);
		    tls_crypt_v2_key.parse(keyfile);
		    tls_crypt_v2_key.extract_key(tls_key);
		    tls_crypt_v2_key.extract_wkc(wkc);
		  }
		else
		  {
		    // in server mode this is a PEM encoded tls-crypt-v2 server key
		    TLSCryptV2ServerKey tls_crypt_v2_key;
		    tls_crypt_v2_key.parse(keyfile);
		    tls_crypt_v2_key.extract_key(tls_key);
		  }
		tls_crypt_v2 = true;
	      }
	  }
	}

	// key-direction
	{
	  if (key_direction >= -1 && key_direction <= 1)
	    {
	      const Option *o = opt.get_ptr(relay_prefix("key-direction"));
	      if (o)
		{
		  const std::string& dir = o->get(1, 16);
		  if (dir == "0")
		    key_direction = 0;
		  else if (dir == "1")
		    key_direction = 1;
		  else if (dir == "bidirectional" || dir == "bi")
		    key_direction = -1;
		  else
		    throw proto_option_error("bad key-direction parameter");
		}
	    }
	  else
	    throw proto_option_error("bad key-direction default");
	}

	// compression
	{
	  const Option *o = opt.get_ptr("compress");
	  if (o)
	    {
	      if (o->size() >= 2)
		{
		  const std::string meth_name = o->get(1, 128);
		  CompressContext::Type meth = CompressContext::parse_method(meth_name);
		  if (meth == CompressContext::NONE)
		    OPENVPN_THROW(proto_option_error, "Unknown compressor: '" << meth_name << '\'');
		  comp_ctx = CompressContext(pco.is_comp() ? meth : CompressContext::stub(meth), pco.is_comp_asym());
		}
	      else
		comp_ctx = CompressContext(pco.is_comp() ? CompressContext::ANY : CompressContext::COMP_STUB, pco.is_comp_asym());
	    }
	  else
	    {
	      o = opt.get_ptr("comp-lzo");
	      if (o)
		{
		  if (o->size() == 2 && o->ref(1) == "no")
		    {
		      // On the client, by using ANY instead of ANY_LZO, we are telling the server
		      // that it's okay to use any of our supported compression methods.
		      comp_ctx = CompressContext(pco.is_comp() ? CompressContext::ANY : CompressContext::LZO_STUB, pco.is_comp_asym());
		    }
		  else
		    {
		      comp_ctx = CompressContext(pco.is_comp() ? CompressContext::LZO : CompressContext::LZO_STUB, pco.is_comp_asym());
		    }
		}
	    }
	}

	// tun-mtu
	tun_mtu = parse_tun_mtu(opt, tun_mtu);

	// mssfix
	mss_parms.parse(opt, true);

	// load parameters that can be present in both config file or pushed options
	load_common(opt, pco, server ? LOAD_COMMON_SERVER : LOAD_COMMON_CLIENT);
      }

      // load options string pushed by server
      void process_push(const OptionList& opt, const ProtoContextOptions& pco)
      {
	// data channel
	{
	  // cipher
	  std::string new_cipher;
	  try {
	    const Option *o = opt.get_ptr("cipher");
	    if (o)
	      {
		new_cipher = o->get(1, 128);
		if (new_cipher != "none")
		  dc.set_cipher(CryptoAlgs::lookup(new_cipher));
	      }
	  }
	  catch (const std::exception& e)
	    {
	      OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed cipher '" << new_cipher << "': " << e.what());
	    }

	  // digest
	  std::string new_digest;
	  try {
	    const Option *o = opt.get_ptr("auth");
	    if (o)
	      {
		new_digest = o->get(1, 128);
		if (new_digest != "none")
		  dc.set_digest(CryptoAlgs::lookup(new_digest));
	      }
	  }
	  catch (const std::exception& e)
	    {
	      OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed digest '" << new_digest << "': " << e.what());
	    }
	}

	// compression
	std::string new_comp;
	try {
	  const Option *o;
	  o = opt.get_ptr("compress");
	  if (o)
	    {
	      new_comp = o->get(1, 128);
	      CompressContext::Type meth = CompressContext::parse_method(new_comp);
	      if (meth != CompressContext::NONE)
		{
		  // if compression is not availabe, CompressContext ctor throws an exception
		  if (pco.is_comp())
		    comp_ctx = CompressContext(meth, pco.is_comp_asym());
		  else
		    {
		      // server pushes compression but client has compression disabled
		      // degrade to asymmetric compression (downlink only)
		      comp_ctx = CompressContext(meth, true);
		      if (!comp_ctx.is_any_stub(meth))
		        {
			  OPENVPN_LOG("Server has pushed compressor "
				      << comp_ctx.str()
			              << ", but client has disabled compression, switching to asymmetric");
		        }
		    }
		}
	    }
	  else
	    {
	      o = opt.get_ptr("comp-lzo");
	      if (o)
		{
		  if (o->size() == 2 && o->ref(1) == "no")
		    {
		      comp_ctx = CompressContext(CompressContext::LZO_STUB, false);
		    }
		  else
		    {
		      comp_ctx = CompressContext(pco.is_comp() ? CompressContext::LZO : CompressContext::LZO_STUB, pco.is_comp_asym());
		    }
		}
	    }
	}
	catch (const std::exception& e)
	  {
	    OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed compressor '" << new_comp << "': " << e.what());
	  }

	// peer ID
	try {
	  const Option *o = opt.get_ptr("peer-id");
	  if (o)
	    {
	      bool status = parse_number_validate<int>(o->get(1, 16),
						       16,
						       -1,
						       0xFFFFFE,
						       &remote_peer_id);
	      if (!status)
		throw Exception("parse/range issue");
	      enable_op32 = true;
	    }
	}
	catch (const std::exception& e)
	  {
	    OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed peer-id: " << e.what());
	  }

	try {
	  // load parameters that can be present in both config file or pushed options
	  load_common(opt, pco, LOAD_COMMON_CLIENT_PUSHED);
	}
	catch (const std::exception& e)
	  {
	    OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed parameter: " << e.what());
	  }

	// show negotiated options
	OPENVPN_LOG_STRING_PROTO(show_options());
      }

      std::string show_options() const
      {
	std::ostringstream os;
	os << "PROTOCOL OPTIONS:" << std::endl;
	os << "  cipher: " << CryptoAlgs::name(dc.cipher()) << std::endl;
	os << "  digest: " << CryptoAlgs::name(dc.digest()) << std::endl;
	os << "  compress: " << comp_ctx.str() << std::endl;
	os << "  peer ID: " << remote_peer_id << std::endl;
	return os.str();
      }

      void set_pid_mode(const bool tcp_linear)
      {
	if (protocol.is_udp() || !tcp_linear)
	  pid_mode = PacketIDReceive::UDP_MODE;
	else if (protocol.is_tcp())
	  pid_mode = PacketIDReceive::TCP_MODE;
	else
	  throw proto_option_error("transport protocol undefined");
      }

      void set_protocol(const Protocol& p)
      {
	// adjust options for new transport protocol
	protocol = p;
	set_pid_mode(false);
      }

      void set_tls_auth_digest(const CryptoAlgs::Type digest)
      {
	tls_auth_context = tls_auth_factory->new_obj(digest);
      }

      void set_tls_crypt_algs(const CryptoAlgs::Type digest,
			      const CryptoAlgs::Type cipher)
      {
	tls_crypt_context = tls_crypt_factory->new_obj(digest, cipher);
      }

      void set_xmit_creds(const bool xmit_creds_arg)
      {
	xmit_creds = xmit_creds_arg;
      }

      bool tls_auth_enabled() const
      {
	return tls_key.defined() && tls_auth_context;
      }

      bool tls_crypt_enabled() const
      {
	return tls_key.defined() && tls_crypt_context;
      }

      bool tls_crypt_v2_enabled() const
      {
	return tls_crypt_enabled() && tls_crypt_v2;
      }

      // generate a string summarizing options that will be
      // transmitted to peer for options consistency check
      std::string options_string()
      {
	if (!initial_options.empty())
	  return initial_options;

	std::ostringstream out;

	const bool server = ssl_factory->mode().is_server();
	const unsigned int l2extra = (layer() == Layer::OSI_LAYER_2 ? 32 : 0);

	out << "V4";

	out << ",dev-type " << layer.dev_type();
	out << ",link-mtu " << tun_mtu + link_mtu_adjust() + l2extra;
	out << ",tun-mtu " << tun_mtu + l2extra;
	out << ",proto " << protocol.str_client(true);

	{
	  const char *compstr = comp_ctx.options_string();
	  if (compstr)
	    out << ',' << compstr;
	}

	if (tls_auth_context && (key_direction >= 0))
	  out << ",keydir " << key_direction;

	out << ",cipher " << CryptoAlgs::name(dc.cipher(), "[null-cipher]");
	out << ",auth " << CryptoAlgs::name(dc.digest(), "[null-digest]");
	out << ",keysize " << (CryptoAlgs::key_length(dc.cipher()) * 8);

	if (tls_auth_context)
	  out << ",tls-auth";

	// sending tls-crypt does not make sense. If we got to this point it
	// means that tls-crypt was already there and it worked fine.
	// tls-auth has to be kept for backward compatibility as it is there
	// since a bit.

	out << ",key-method 2";

	if (server)
	  out << ",tls-server";
	else
	  out << ",tls-client";

	initial_options = out.str();

	return initial_options;
      }

      // generate a string summarizing information about the client
      // including capabilities
      std::string peer_info_string() const
      {
	std::ostringstream out;
	const char *compstr = nullptr;

	out << "IV_VER=" << OPENVPN_VERSION << '\n';
	out << "IV_PLAT=" << platform_name() << '\n';
	if (!force_aes_cbc_ciphersuites)
	  {
	    out << "IV_NCP=2\n"; // negotiable crypto parameters V2
	    out << "IV_TCPNL=1\n"; // supports TCP non-linear packet ID
	    out << "IV_PROTO=2\n"; // supports op32 and P_DATA_V2
	    compstr = comp_ctx.peer_info_string();
	  }
	else
	  compstr = comp_ctx.peer_info_string_v1();
	if (compstr)
	  out << compstr;
	if (extra_peer_info)
	  out << extra_peer_info->to_string();
	if (is_bs64_cipher(dc.cipher()))
	  out << "IV_BS64DL=1\n"; // indicate support for data limits when using 64-bit block-size ciphers, version 1 (CVE-2016-6329)
	if (relay_mode)
	  out << "IV_RELAY=1\n";
	const std::string ret = out.str();
	OPENVPN_LOG_PROTO("Peer Info:" << std::endl << ret);
	return ret;
      }

      // Used to generate link_mtu option sent to peer.
      // Not const because dc.context() caches the DC context.
      unsigned int link_mtu_adjust()
      {
	const size_t adj = protocol.extra_transport_bytes() + // extra 2 bytes for TCP-streamed packet length
          (enable_op32 ? 4 : 1) +                        // leading op
	  comp_ctx.extra_payload_bytes() +               // compression header
	  PacketID::size(PacketID::SHORT_FORM) +         // sequence number
	  dc.context().encap_overhead();                 // data channel crypto layer overhead
	return (unsigned int)adj;
      }

    private:
      enum LoadCommonType {
	LOAD_COMMON_SERVER,
	LOAD_COMMON_CLIENT,
	LOAD_COMMON_CLIENT_PUSHED,
      };

      // load parameters that can be present in both config file or pushed options
      void load_common(const OptionList& opt, const ProtoContextOptions& pco,
		       const LoadCommonType type)
      {
	// duration parms
	load_duration_parm(renegotiate, "reneg-sec", opt, 10, false, false);
	expire = renegotiate;
	load_duration_parm(expire, "tran-window", opt, 10, false, false);
	expire += renegotiate;
	load_duration_parm(handshake_window, "hand-window", opt, 10, false, false);
	if (is_bs64_cipher(dc.cipher())) // special data limits for 64-bit block-size ciphers (CVE-2016-6329)
	  {
	    become_primary = Time::Duration::seconds(5);
	    tls_timeout = Time::Duration::milliseconds(1000);
	  }
	else
	  become_primary = Time::Duration::seconds(std::min(handshake_window.to_seconds(),
							    renegotiate.to_seconds() / 2));
	load_duration_parm(become_primary, "become-primary", opt, 0, false, false);
	load_duration_parm(tls_timeout, "tls-timeout", opt, 100, false, true);

	if (type == LOAD_COMMON_SERVER)
	  renegotiate += handshake_window; // avoid renegotiation collision with client

	// keepalive, ping, ping-restart
	{
	  const Option *o = opt.get_ptr("keepalive");
	  if (o)
	    {
	      set_duration_parm(keepalive_ping, "keepalive ping", o->get(1, 16), 1, false, false);
	      set_duration_parm(keepalive_timeout, "keepalive timeout", o->get(2, 16), 1, type == LOAD_COMMON_SERVER, false);
	    }
	  else
	    {
	      load_duration_parm(keepalive_ping, "ping", opt, 1, false, false);
	      load_duration_parm(keepalive_timeout, "ping-restart", opt, 1, false, false);
	    }
	}
      }

      std::string relay_prefix(const char *optname) const
      {
	std::string ret;
	if (relay_mode)
	  ret = "relay-";
	ret += optname;
	return ret;
      }
    };

    // Used to describe an incoming network packet
    class PacketType
    {
      friend class ProtoContext;

      enum {
	DEFINED=1<<0,     // packet is valid (otherwise invalid)
	CONTROL=1<<1,     // packet for control channel (otherwise for data channel)
	SECONDARY=1<<2,   // packet is associated with secondary KeyContext (otherwise primary)
	SOFT_RESET=1<<3,  // packet is a CONTROL_SOFT_RESET_V1 msg indicating a request for SSL/TLS renegotiate
      };

    public:
      bool is_defined() const { return flags & DEFINED; }
      bool is_control() const { return (flags & (CONTROL|DEFINED)) == (CONTROL|DEFINED); }
      bool is_data()    const { return (flags & (CONTROL|DEFINED)) == DEFINED; }
      bool is_soft_reset() const { return (flags & (CONTROL|DEFINED|SECONDARY|SOFT_RESET))
	                                        == (CONTROL|DEFINED|SECONDARY|SOFT_RESET); }
      int peer_id() const { return peer_id_; }

    private:
      PacketType(const Buffer& buf, class ProtoContext& proto)
	: flags(0), opcode(INVALID_OPCODE), peer_id_(-1)
      {
	if (likely(buf.size()))
	  {
	    // get packet header byte
	    const unsigned int op = buf[0];

	    // examine opcode
	    {
	      const unsigned int opc = opcode_extract(op);
	      switch (opc)
		{
		case CONTROL_SOFT_RESET_V1:
		case CONTROL_V1:
		case ACK_V1:
		  {
		    flags |= CONTROL;
		    opcode = opc;
		    break;
		  }
		case DATA_V2:
		  {
		    if (unlikely(buf.size() < 4))
		      return;
		    const int opi = ntohl(*(const std::uint32_t *)buf.c_data()) & 0x00FFFFFF;
		    if (opi != OP_PEER_ID_UNDEF)
		      peer_id_ = opi;
		    opcode = opc;
		    break;
		  }
		case DATA_V1:
		  {
		    opcode = opc;
		    break;
		  }
		case CONTROL_HARD_RESET_CLIENT_V2:
		case CONTROL_HARD_RESET_CLIENT_V3:
		  {
		    if (!proto.is_server())
		      return;
		    flags |= CONTROL;
		    opcode = opc;
		    break;
		  }
		case CONTROL_HARD_RESET_SERVER_V2:
		  {
		    if (proto.is_server())
		      return;
		    flags |= CONTROL;
		    opcode = opc;
		    break;
		  }
		default:
		  return;
		}
	    }

	    // examine key ID
	    {
	      const unsigned int kid = key_id_extract(op);
	      if (proto.primary && kid == proto.primary->key_id())
		flags |= DEFINED;
	      else if (proto.secondary && kid == proto.secondary->key_id())
		flags |= (DEFINED | SECONDARY);
	      else if (opcode == CONTROL_SOFT_RESET_V1 && kid == proto.upcoming_key_id)
		flags |= (DEFINED | SECONDARY | SOFT_RESET);
	    }
	  }
      }

      unsigned int flags;
      unsigned int opcode;
      int peer_id_;
    };

    static const char *opcode_name(const unsigned int opcode)
    {
      switch (opcode)
	{
	case CONTROL_SOFT_RESET_V1:
	  return "CONTROL_SOFT_RESET_V1";
	case CONTROL_V1:
	  return "CONTROL_V1";
	case ACK_V1:
	  return "ACK_V1";
	case DATA_V1:
	  return "DATA_V1";
	case DATA_V2:
	  return "DATA_V2";
	case CONTROL_HARD_RESET_CLIENT_V2:
	  return "CONTROL_HARD_RESET_CLIENT_V2";
	case CONTROL_HARD_RESET_CLIENT_V3:
	  return "CONTROL_HARD_RESET_CLIENT_V3";
	case CONTROL_HARD_RESET_SERVER_V2:
	  return "CONTROL_HARD_RESET_SERVER_V2";
	}
      return nullptr;
    }

    std::string dump_packet(const Buffer& buf)
    {
      std::ostringstream out;
      try {
	Buffer b(buf);
	const size_t orig_size = b.size();
	const unsigned int op = b.pop_front();

	const unsigned int opcode = opcode_extract(op);
	const char *op_name = opcode_name(opcode);
	if (op_name)
	  out << op_name << '/' << key_id_extract(op);
	else
	  return "BAD_PACKET";

	if (opcode == DATA_V1 || opcode == DATA_V2)
	  {
	    if (opcode == DATA_V2)
	      {
		const unsigned int p1 = b.pop_front();
		const unsigned int p2 = b.pop_front();
		const unsigned int p3 = b.pop_front();
		const unsigned int peer_id = (p1<<16) + (p2<<8) + p3;
		if (peer_id != 0xFFFFFF)
		  out << " PEER_ID=" << peer_id;
	      }
	    out << " SIZE=" << b.size() << '/' << orig_size;
	  }
	else
	  {
	    {
	      ProtoSessionID src_psid(b);
	      out << " SRC_PSID=" << src_psid.str();
	    }

	    if (tls_wrap_mode == TLS_CRYPT)
	      {
		PacketID pid;
		pid.read(b, PacketID::LONG_FORM);
		out << " PID=" << pid.str();

		const unsigned char *hmac = b.read_alloc(hmac_size);
		out << " HMAC=" << render_hex(hmac, hmac_size);

		// nothing else to print as the content is encrypted beyond this point
		out << " TLS-CRYPT ENCRYPTED";
	      }
	    else
	      {
		if (tls_wrap_mode == TLS_AUTH)
	          {
		    const unsigned char *hmac = b.read_alloc(hmac_size);
		    out << " HMAC=" << render_hex(hmac, hmac_size);

		    PacketID pid;
		    pid.read(b, PacketID::LONG_FORM);
		    out << " PID=" << pid.str();
	          }

	        ReliableAck ack(0);
	        ack.read(b);
	        const bool dest_psid_defined = !ack.empty();
	        out << " ACK=[";
	        while (!ack.empty())
	          {
		    out << " " << ack.front();
		    ack.pop_front();
	          }
	        out << " ]";

	        if (dest_psid_defined)
	          {
		    ProtoSessionID dest_psid(b);
		    out << " DEST_PSID=" << dest_psid.str();
	          }

	        if (opcode != ACK_V1)
	          out << " MSG_ID=" << ReliableAck::read_id(b);
	      }
	    if (opcode != ACK_V1)
	      out << " SIZE=" << b.size() << '/' << orig_size;
	  }
#ifdef OPENVPN_DEBUG_PROTO_DUMP
	out << '\n' << string::trim_crlf_copy(dump_hex(buf));
#endif
      }
      catch (const std::exception& e)
	{
	  out << " EXCEPTION: " << e.what();
	}
      return out.str();
    }

  protected:

    // used for reading/writing authentication strings (username, password, etc.)

    static void write_string_length(const size_t size, Buffer& buf)
    {
      if (size > 0xFFFF)
	throw proto_error("auth_string_overflow");
      const std::uint16_t net_size = htons(size);
      buf.write((const unsigned char *)&net_size, sizeof(net_size));
    }

    static size_t read_string_length(Buffer& buf)
    {
      if (buf.size())
	{
	  std::uint16_t net_size;
	  buf.read((unsigned char *)&net_size, sizeof(net_size));
	  return ntohs(net_size);
	}
      else
	return 0;
    }

    template <typename S>
    static void write_auth_string(const S& str, Buffer& buf)
    {
      const size_t len = str.length();
      if (len)
	{
	  write_string_length(len+1, buf);
	  buf.write((const unsigned char *)str.c_str(), len);
	  buf.null_terminate();
	}
      else
	write_string_length(0, buf);
    }

    template <typename S>
    static S read_auth_string(Buffer& buf)
    {
      const size_t len = read_string_length(buf);
      if (len)
	{
	  const char *data = (const char *) buf.read_alloc(len);
	  if (len > 1)
	    return S(data, len-1);
	}
      return S();
    }

    template <typename S>
    static void write_control_string(const S& str, Buffer& buf)
    {
      const size_t len = str.length();
      buf.write((const unsigned char *)str.c_str(), len);
      buf.null_terminate();
    }

    template <typename S>
    static S read_control_string(const Buffer& buf)
    {
      size_t size = buf.size();
      if (size)
	{
	  if (buf[size-1] == 0)
	    --size;
	  if (size)
	    return S((const char *)buf.c_data(), size);
	}
      return S();
    }

    template <typename S>
    void write_control_string(const S& str)
    {
      const size_t len = str.length();
      BufferPtr bp = new BufferAllocated(len+1, 0);
      write_control_string(str, *bp);
      control_send(std::move(bp));
    }

    static unsigned char *skip_string(Buffer& buf)
    {
      const size_t len = read_string_length(buf);
      return buf.read_alloc(len);
    }

    static void write_empty_string(Buffer& buf)
    {
      write_string_length(0, buf);
    }

    // Packet structure for managing network packets, passed as a template
    // parameter to ProtoStackBase
    class Packet
    {
      friend class ProtoContext;

    public:
      Packet()
      {
	reset_non_buf();
      }

      Packet(BufferPtr&& buf_arg, const unsigned int opcode_arg = CONTROL_V1)
	: opcode(opcode_arg), buf(std::move(buf_arg))
      {
      }

      void reset()
      {
	reset_non_buf();
	buf.reset();
      }

      void frame_prepare(const Frame& frame, const unsigned int context)
      {
	if (!buf)
	  buf.reset(new BufferAllocated());
	frame.prepare(context, *buf);
      }

      bool is_raw() const { return opcode != CONTROL_V1; }
      operator bool() const { return bool(buf); }
      const BufferPtr& buffer_ptr() { return buf; }
      const Buffer& buffer() const { return *buf; }

    private:
      void reset_non_buf()
      {
	opcode = INVALID_OPCODE;
      }

      unsigned int opcode;
      BufferPtr buf;
    };

    // KeyContext encapsulates a single SSL/TLS session.
    // ProtoStackBase uses CRTP-based static polymorphism for method callbacks.
    class KeyContext : ProtoStackBase<Packet, KeyContext>, public RC<thread_unsafe_refcount>
    {
      typedef ProtoStackBase<Packet, KeyContext> Base;
      friend Base;
      typedef Base::ReliableSend ReliableSend;
      typedef Base::ReliableRecv ReliableRecv;

      // ProtoStackBase protected vars
      using Base::now;
      using Base::rel_recv;
      using Base::rel_send;
      using Base::xmit_acks;

      // ProtoStackBase member functions
      using Base::start_handshake;
      using Base::raw_send;
      using Base::send_pending_acks;

      // Helper for handling deferred data channel setup,
      // for example if cipher/digest are pushed.
      struct DataChannelKey
      {
	DataChannelKey() : rekey_defined(false) {}

	OpenVPNStaticKey key;
	bool rekey_defined;
	CryptoDCInstance::RekeyType rekey_type;
      };

    public:
      typedef RCPtr<KeyContext> Ptr;

      OPENVPN_SIMPLE_EXCEPTION(tls_crypt_unwrap_wkc_error);

      // KeyContext events occur on two basic key types:
      //   Primary Key -- the key we transmit/encrypt on.
      //   Secondary Key -- new keys and retiring keys.
      //
      // The very first key created (key_id == 0) is a
      // primary key.  Subsequently created keys are always,
      // at least initially, secondary keys.  Secondary keys
      // promote to primary via the KEV_BECOME_PRIMARY event
      // (actually KEV_BECOME_PRIMARY swaps the primary and
      // secondary keys, so the old primary is demoted
      // to secondary and marked for expiration).
      //
      // Secondary keys are created by:
      // 1. locally-generated soft renegotiation requests, and
      // 2. peer-requested soft renegotiation requests.
      // In each case, any previous secondary key will be
      // wiped (including a secondary key that exists due to
      // demotion of a previous primary key that has been marked
      // for expiration).
      enum EventType {
	KEV_NONE,

	// KeyContext has reached the ACTIVE state, occurs on both
	// primary and secondary.
	KEV_ACTIVE,

	// SSL/TLS negotiation must complete by this time.  If this
	// event is hit on the first primary (i.e. first KeyContext
	// with key_id == 0), it is fatal to the session and will
	// trigger a disconnect/reconnect.  If it's hit on the
	// secondary, it will trigger a soft renegotiation.
	KEV_NEGOTIATE,

	// When a KeyContext (normally the secondary) is scheduled
	// to transition to the primary state.
	KEV_BECOME_PRIMARY,

	// Waiting for condition on secondary (usually
	// dataflow-based) to trigger KEV_BECOME_PRIMARY.
	KEV_PRIMARY_PENDING,

	// Start renegotiating a new KeyContext on secondary
	// (ignored unless originating on primary).
	KEV_RENEGOTIATE,

	// Trigger a renegotiation originating from either
	// primary or secondary.
	KEV_RENEGOTIATE_FORCE,

	// Queue delayed renegotiation request from secondary
	// to take effect after KEV_BECOME_PRIMARY.
	KEV_RENEGOTIATE_QUEUE,

	// Expiration of KeyContext.
	KEV_EXPIRE,
      };

      // for debugging
      static const char *event_type_string(const EventType et)
      {
	switch (et)
	  {
	  case KEV_NONE:
	    return "KEV_NONE";
	  case KEV_ACTIVE:
	    return "KEV_ACTIVE";
	  case KEV_NEGOTIATE:
	    return "KEV_NEGOTIATE";
	  case KEV_BECOME_PRIMARY:
	    return "KEV_BECOME_PRIMARY";
	  case KEV_PRIMARY_PENDING:
	    return "KEV_PRIMARY_PENDING";
	  case KEV_RENEGOTIATE:
	    return "KEV_RENEGOTIATE";
	  case KEV_RENEGOTIATE_FORCE:
	    return "KEV_RENEGOTIATE_FORCE";
	  case KEV_RENEGOTIATE_QUEUE:
	    return "KEV_RENEGOTIATE_QUEUE";
	  case KEV_EXPIRE:
	    return "KEV_EXPIRE";
	  default:
	    return "KEV_?";
	  }
      }

      KeyContext(ProtoContext& p, const bool initiator)
	: Base(*p.config->ssl_factory,
	       p.config->now, p.config->tls_timeout,
	       p.config->frame, p.stats,
	       p.config->reliable_window, p.config->max_ack_list),
	  proto(p),
	  state(STATE_UNDEF),
	  crypto_flags(0),
	  dirty(0),
	  key_limit_renegotiation_fired(false),
	  tlsprf(p.config->tlsprf_factory->new_obj(p.is_server()))
      {
	// reliable protocol?
	set_protocol(proto.config->protocol);

	// get key_id from parent
	key_id_ = proto.next_key_id();

	// set initial state
	set_state((proto.is_server() ? S_INITIAL : C_INITIAL) + (initiator ? 0 : 1));

	// cache stuff that we need to access in hot path
	cache_op32();

	// remember when we were constructed
	construct_time = *now;

	// set must-negotiate-by time
	set_event(KEV_NONE, KEV_NEGOTIATE, construct_time + proto.config->handshake_window);
      }

      void set_protocol(const Protocol& p)
      {
	is_reliable = p.is_reliable(); // cache is_reliable state locally
      }

      uint32_t get_tls_warnings() const
      {
	return Base::get_tls_warnings();
      }

      // need to call only on the initiator side of the connection
      void start()
      {
	if (state == C_INITIAL || state == S_INITIAL)
	  {
	    send_reset();
	    set_state(state+1);
	    dirty = true;
	  }
      }

      // control channel flush
      void flush()
      {
	if (dirty)
	  {
	    post_ack_action();
	    Base::flush();
	    send_pending_acks();
	    dirty = false;
	  }
      }

      void invalidate(const Error::Type reason)
      {
	Base::invalidate(reason);
      }

      // retransmit packets as part of reliability layer
      void retransmit()
      {
	// note that we don't set dirty here
	Base::retransmit();
      }

      // when should we next call retransmit method
      Time next_retransmit() const
      {
	const Time t = Base::next_retransmit();
	if (t <= next_event_time)
	  return t;
	else
	  return next_event_time;
      }

      void app_send_validate(BufferPtr&& bp)
      {
	if (bp->size() > APP_MSG_MAX)
	  throw proto_error("app_send: sent control message is too large");
	Base::app_send(std::move(bp));
      }

      // send app-level cleartext data to peer via SSL
      void app_send(BufferPtr&& bp)
      {
	if (state >= ACTIVE)
	  {
	    app_send_validate(std::move(bp));
	    dirty = true;
	  }
	else
	  app_pre_write_queue.push_back(bp);
      }

      // pass received ciphertext packets on network to SSL/reliability layers
      bool net_recv(Packet&& pkt)
      {
	const bool ret = Base::net_recv(std::move(pkt));
	dirty = true;
	return ret;
      }

      // data channel encrypt
      void encrypt(BufferAllocated& buf)
      {
	if (state >= ACTIVE
	    && (crypto_flags & CryptoDCInstance::CRYPTO_DEFINED)
	    && !invalidated())
	  {
	    // compress and encrypt packet and prepend op header
	    const bool pid_wrap = do_encrypt(buf, true);

	    // Trigger a new SSL/TLS negotiation if packet ID (a 32-bit unsigned int)
	    // is getting close to wrapping around.  If it wraps back to 0 without
	    // a renegotiation, it would cause the replay protection logic to wrongly
	    // think that all further packets are replays.
	    if (pid_wrap)
	      schedule_key_limit_renegotiation();
	  }
	else
	  buf.reset_size(); // no crypto context available
      }

      // data channel decrypt
      void decrypt(BufferAllocated& buf)
      {
	try {
	  if (state >= ACTIVE
	      && (crypto_flags & CryptoDCInstance::CRYPTO_DEFINED)
	      && !invalidated())
	    {
	      // Knock off leading op from buffer, but pass the 32-bit version to
	      // decrypt so it can be used as Additional Data for packet authentication.
	      const size_t head_size = op_head_size(buf[0]);
	      const unsigned char *op32 = (head_size == OP_SIZE_V2) ? buf.c_data() : nullptr;
	      buf.advance(head_size);

	      // decrypt packet
	      const Error::Type err = crypto->decrypt(buf, now->seconds_since_epoch(), op32);
	      if (err)
		{
		  proto.stats->error(err);
		  if (proto.is_tcp() && (err == Error::DECRYPT_ERROR || err == Error::HMAC_ERROR))
		    invalidate(err);
		}

	      // trigger renegotiation if we hit decrypt data limit
	      if (data_limit)
		data_limit_add(DataLimit::Decrypt, buf.size());

	      // decompress packet
	      if (compress)
		compress->decompress(buf);

	      // set MSS for segments server can receive
	      if (proto.config->mss_inter > 0)
		MSSFix::mssfix(buf, proto.config->mss_inter);
	    }
	  else
	    buf.reset_size(); // no crypto context available
	}
	catch (BufferException&)
	  {
	    proto.stats->error(Error::BUFFER_ERROR);
	    buf.reset_size();
	    if (proto.is_tcp())
	      invalidate(Error::BUFFER_ERROR);
	  }
      }

      // usually called by parent ProtoContext object when this KeyContext
      // has been retired.
      void prepare_expire(const EventType current_ev = KeyContext::KEV_NONE)
      {
	set_event(current_ev,
		  KEV_EXPIRE,
		  key_limit_renegotiation_fired ? data_limit_expire() : construct_time + proto.config->expire);
      }

      // set a default next event, if unspecified
      void set_next_event_if_unspecified()
      {
	if (next_event == KEV_NONE && !invalidated())
	  prepare_expire();
      }

      // set a key limit renegotiation event at time t
      void key_limit_reneg(const EventType ev, const Time& t)
      {
	if (t.defined())
	  set_event(KEV_NONE, ev, t + Time::Duration::seconds(proto.is_server() ? 2 : 1));
      }

      // return time of upcoming KEV_BECOME_PRIMARY event
      Time become_primary_time()
      {
	if (next_event == KEV_BECOME_PRIMARY)
	  return next_event_time;
	else
	  return Time();
      }

      // is an KEV_x event pending?
      bool event_pending()
      {
	if (current_event == KEV_NONE && *now >= next_event_time)
	  process_next_event();
	return current_event != KEV_NONE;
      }

      // get KEV_x event
      EventType get_event() const { return current_event; }

      // clear KEV_x event
      void reset_event() { current_event = KEV_NONE; }

      // was session invalidated by an exception?
      bool invalidated() const { return Base::invalidated(); }

      // Reason for invalidation
      Error::Type invalidation_reason() const { return Base::invalidation_reason(); }

      // our Key ID in the OpenVPN protocol
      unsigned int key_id() const { return key_id_; }

      // indicates that data channel is keyed and ready to encrypt/decrypt packets
      bool data_channel_ready() const { return state >= ACTIVE; }

      bool is_dirty() const { return dirty; }

      // notification from parent of rekey operation
      void rekey(const CryptoDCInstance::RekeyType type)
      {
	if (crypto)
	  crypto->rekey(type);
	else if (data_channel_key)
	  {
	    // save for deferred processing
	    data_channel_key->rekey_type = type;
	    data_channel_key->rekey_defined = true;
	  }
      }

      // time that our state transitioned to ACTIVE
      Time reached_active() const { return reached_active_time_; }

      // transmit a keepalive message to peer
      void send_keepalive()
      {
	send_data_channel_message(proto_context_private::keepalive_message,
				  sizeof(proto_context_private::keepalive_message));
      }

      // send explicit-exit-notify message to peer
      void send_explicit_exit_notify()
      {
#ifndef OPENVPN_DISABLE_EXPLICIT_EXIT // explicit exit should always be enabled in production
	if (crypto_flags & CryptoDCInstance::EXPLICIT_EXIT_NOTIFY_DEFINED)
	  crypto->explicit_exit_notify();
	else
	  send_data_channel_message(proto_context_private::explicit_exit_notify_message,
				    sizeof(proto_context_private::explicit_exit_notify_message));
#endif
      }

      // general purpose method for sending constant string messages
      // to peer via data channel
      void send_data_channel_message(const unsigned char *data, const size_t size)
      {
	if (state >= ACTIVE
	    && (crypto_flags & CryptoDCInstance::CRYPTO_DEFINED)
	    && !invalidated())
	  {
	    // allocate packet
	    Packet pkt;
	    pkt.frame_prepare(*proto.config->frame, Frame::WRITE_DC_MSG);

	    // write keepalive message
	    pkt.buf->write(data, size);

	    // process packet for transmission
	    do_encrypt(*pkt.buf, false); // set compress hint to "no"

	    // send it
	    proto.net_send(key_id_, pkt);
	  }
      }

      // validate the integrity of a packet
      static bool validate(const Buffer& net_buf, ProtoContext& proto, TimePtr now)
      {
	try {
	  Buffer recv(net_buf);

	  switch (proto.tls_wrap_mode)
	  {
	    case TLS_AUTH:
	      return validate_tls_auth(recv, proto, now);
	    case TLS_CRYPT_V2:
	      if (opcode_extract(recv[0]) == CONTROL_HARD_RESET_CLIENT_V3)
		{
		  // skip validation of HARD_RESET_V3 because the tls-crypt
		  // engine has not been initialized yet
		  OPENVPN_LOG_PROTO_VERBOSE("SKIPPING VALIDATION OF HARD_RESET_V3");
		  return true;
		}
	      /* no break */
	    case TLS_CRYPT:
	      return validate_tls_crypt(recv, proto, now);
	    case TLS_PLAIN:
	      return validate_tls_plain(recv, proto, now);
	    }
	}
	catch (BufferException& e)
	  {
	    OPENVPN_LOG_PROTO_VERBOSE("validate() exception: " << e.what());
	  }
	return false;
      }

      // Initialize the components of the OpenVPN data channel protocol
      void init_data_channel()
      {
	// set up crypto for data channel
	if (data_channel_key)
	  {
	    bool enable_compress = true;
	    Config& c = *proto.config;
	    const unsigned int key_dir = proto.is_server() ? OpenVPNStaticKey::INVERSE : OpenVPNStaticKey::NORMAL;
	    const OpenVPNStaticKey& key = data_channel_key->key;

	    // special data limits for 64-bit block-size ciphers (CVE-2016-6329)
	    if (is_bs64_cipher(c.dc.cipher()))
	      {
		DataLimit::Parameters dp;
		dp.encrypt_red_limit = OPENVPN_BS64_DATA_LIMIT;
		dp.decrypt_red_limit = OPENVPN_BS64_DATA_LIMIT;
		OPENVPN_LOG_PROTO("Per-Key Data Limit: " << dp.encrypt_red_limit << '/' << dp.decrypt_red_limit);
		data_limit.reset(new DataLimit(dp));
	      }

	    // build crypto context for data channel encryption/decryption
	    crypto = c.dc.context().new_obj(key_id_);
	    crypto_flags = crypto->defined();

	    if (crypto_flags & CryptoDCInstance::CIPHER_DEFINED)
	      crypto->init_cipher(key.slice(OpenVPNStaticKey::CIPHER | OpenVPNStaticKey::ENCRYPT | key_dir),
				  key.slice(OpenVPNStaticKey::CIPHER | OpenVPNStaticKey::DECRYPT | key_dir));

	    if (crypto_flags & CryptoDCInstance::HMAC_DEFINED)
	      crypto->init_hmac(key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::ENCRYPT | key_dir),
				key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::DECRYPT | key_dir));

	    crypto->init_pid(PacketID::SHORT_FORM,
			     c.pid_mode,
			     PacketID::SHORT_FORM,
			     "DATA", int(key_id_),
			     proto.stats);

	    crypto->init_remote_peer_id(c.remote_peer_id);

	    enable_compress = crypto->consider_compression(proto.config->comp_ctx);

	    if (data_channel_key->rekey_defined)
	      crypto->rekey(data_channel_key->rekey_type);
	    data_channel_key.reset();

	    // set up compression for data channel
	    if (enable_compress)
	      compress = proto.config->comp_ctx.new_compressor(proto.config->frame, proto.stats);
	    else
	      compress.reset();

	    // cache op32 for hot path in do_encrypt
	    cache_op32();

	    int crypto_encap = (enable_op32 ? OP_SIZE_V2 : 1) +
			       c.comp_ctx.extra_payload_bytes() +
			       PacketID::size(PacketID::SHORT_FORM) +
			       c.dc.context().encap_overhead();

	    int transport_encap = 0;
	    if (c.mss_parms.mtu)
	      {
		if (proto.is_tcp())
		  transport_encap += sizeof(struct TCPHeader);
		else
		  transport_encap += sizeof(struct UDPHeader);

		if (c.protocol.is_ipv6())
		  transport_encap += sizeof(struct IPv6Header);
		else
		  transport_encap += sizeof(struct IPv4Header);

		transport_encap += c.protocol.extra_transport_bytes();
	      }

	    if (c.mss_parms.mssfix != 0)
	      {
		OPENVPN_LOG_PROTO("MTU mssfix=" << c.mss_parms.mssfix <<
				  " crypto_encap=" << crypto_encap <<
				  " transport_encap=" << transport_encap);
		c.mss_inter = c.mss_parms.mssfix - (crypto_encap + transport_encap);
	      }
	  }
      }

      void data_limit_notify(const DataLimit::Mode cdl_mode,
			     const DataLimit::State cdl_status)
      {
	if (data_limit)
	  data_limit_event(cdl_mode, data_limit->update_state(cdl_mode, cdl_status));
      }

    private:
      static bool validate_tls_auth(Buffer &recv, ProtoContext& proto, TimePtr now)
      {
	const unsigned char *orig_data = recv.data();
	const size_t orig_size = recv.size();

	// advance buffer past initial op byte
	recv.advance(1);

	// get source PSID
	ProtoSessionID src_psid(recv);

	// verify HMAC
	{
	  recv.advance(proto.hmac_size);
	  if (!proto.ta_hmac_recv->ovpn_hmac_cmp(orig_data, orig_size,
						 1 + ProtoSessionID::SIZE,
						 proto.hmac_size,
						 PacketID::size(PacketID::LONG_FORM)))
	    return false;
	}

	// verify source PSID
	if (!proto.psid_peer.match(src_psid))
	  return false;

	// read tls_auth packet ID
	const PacketID pid = proto.ta_pid_recv.read_next(recv);

	// get current time_t
	const PacketID::time_t t = now->seconds_since_epoch();

	// verify tls_auth packet ID
	const bool pid_ok = proto.ta_pid_recv.test_add(pid, t, false);

	// make sure that our own PSID is contained in packet received from peer
	if (ReliableAck::ack_skip(recv))
	  {
	    ProtoSessionID dest_psid(recv);
	    if (!proto.psid_self.match(dest_psid))
	      return false;
	  }

	return pid_ok;
      }

      static bool validate_tls_crypt(Buffer& recv, ProtoContext& proto, TimePtr now)
       {
	const unsigned char *orig_data = recv.data();
	const size_t orig_size = recv.size();

	// advance buffer past initial op byte
	recv.advance(1);
	// get source PSID
	ProtoSessionID src_psid(recv);
	// read tls_auth packet ID
	const PacketID pid = proto.ta_pid_recv.read_next(recv);

	recv.advance(proto.hmac_size);

	const size_t head_size = 1 + ProtoSessionID::SIZE + PacketID::size(PacketID::LONG_FORM);
	const size_t data_offset = head_size + proto.hmac_size;
	if (orig_size < data_offset)
	  return false;

	// we need a buffer to perform the payload decryption and being this a static
	// function we can't use the instance member like in decapsulate_tls_crypt()
	BufferAllocated work;
	proto.config->frame->prepare(Frame::DECRYPT_WORK, work);

	// decrypt payload from 'recv' into 'work'
	const size_t decrypt_bytes = proto.tls_crypt_recv->decrypt(orig_data + head_size,
								   work.data(), work.max_size(),
								   recv.c_data(), recv.size());
	if (!decrypt_bytes)
	  return false;

	work.inc_size(decrypt_bytes);

	// verify HMAC
	if (!proto.tls_crypt_recv->hmac_cmp(orig_data,
					    TLSCryptContext::hmac_offset,
					    work.c_data(), work.size()))
	  return false;

	// verify source PSID
	if (proto.psid_peer.defined())
	  {
	    if (!proto.psid_peer.match(src_psid))
	      return false;
	  }
	else
	  {
	    proto.psid_peer = src_psid;
	  }

	// get current time_t
	const PacketID::time_t t = now->seconds_since_epoch();

	// verify tls_auth packet ID
	const bool pid_ok = proto.ta_pid_recv.test_add(pid, t, false);
	// make sure that our own PSID is contained in packet received from peer
	if (ReliableAck::ack_skip(work))
	  {
	    ProtoSessionID dest_psid(work);
	    if (!proto.psid_self.match(dest_psid))
	      return false;
	  }

	return pid_ok;
       }

      static bool validate_tls_plain(Buffer& recv, ProtoContext& proto, TimePtr now)
      {
	// advance buffer past initial op byte
	recv.advance(1);

	// verify source PSID
	ProtoSessionID src_psid(recv);
	if (!proto.psid_peer.match(src_psid))
	  return false;

	// make sure that our own PSID is contained in packet received from peer
	if (ReliableAck::ack_skip(recv))
	  {
	    ProtoSessionID dest_psid(recv);
	    if (!proto.psid_self.match(dest_psid))
	      return false;
	  }
	return true;
      }

      bool do_encrypt(BufferAllocated& buf, const bool compress_hint)
      {
	bool pid_wrap;

	// set MSS for segments client can receive
	if (proto.config->mss_inter > 0)
	  MSSFix::mssfix(buf, proto.config->mss_inter);

	// compress packet
	if (compress)
	  compress->compress(buf, compress_hint);

	// trigger renegotiation if we hit encrypt data limit
	if (data_limit)
	  data_limit_add(DataLimit::Encrypt, buf.size());

	if (enable_op32)
	  {
	    const std::uint32_t op32 = htonl(op32_compose(DATA_V2, key_id_, remote_peer_id));

	    static_assert(sizeof(op32) == OP_SIZE_V2, "OP_SIZE_V2 inconsistency");

	    // encrypt packet
	    pid_wrap = crypto->encrypt(buf, now->seconds_since_epoch(), (const unsigned char *)&op32);

	    // prepend op
	    buf.prepend((const unsigned char *)&op32, sizeof(op32));
	  }
	else
	  {
	    // encrypt packet
	    pid_wrap = crypto->encrypt(buf, now->seconds_since_epoch(), nullptr);

	    // prepend op
	    buf.push_front(op_compose(DATA_V1, key_id_));
	  }
	return pid_wrap;
      }

      // cache op32 and remote_peer_id
      void cache_op32()
      {
	enable_op32 = proto.config->enable_op32;
	remote_peer_id = proto.config->remote_peer_id;
      }

      void set_state(const int newstate)
      {
	OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " KeyContext[" << key_id_ << "] " << state_string(state) << " -> " << state_string(newstate));
	state = newstate;
      }

      void set_event(const EventType current)
      {
	OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " KeyContext[" << key_id_ << "] " << event_type_string(current));
	current_event = current;
      }

      void set_event(const EventType current, const EventType next, const Time& next_time)
      {
	OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " KeyContext[" << key_id_ << "] " << event_type_string(current) << " -> " << event_type_string(next) << '(' << seconds_until(next_time) << ')');
	current_event = current;
	next_event = next;
	next_event_time = next_time;
      }

      void invalidate_callback() // called by ProtoStackBase when session is invalidated
      {
	reached_active_time_ = Time();
	next_event = KEV_NONE;
	next_event_time = Time::infinite();
      }

      // Trigger a renegotiation based on data flow condition such
      // as per-key data limit or packet ID approaching wraparound.
      void schedule_key_limit_renegotiation()
      {
	if (!key_limit_renegotiation_fired && state >= ACTIVE && !invalidated())
	  {
	    OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " SCHEDULE KEY LIMIT RENEGOTIATION");

	    key_limit_renegotiation_fired = true;
	    proto.stats->error(Error::N_KEY_LIMIT_RENEG);

	    // If primary, renegotiate now (within a second or two).
	    // If secondary, queue the renegotiation request until
	    // key reaches primary.
	    if (next_event == KEV_BECOME_PRIMARY) // secondary key before transition to primary?
	      set_event(KEV_RENEGOTIATE_QUEUE);   // reneg request crosses over to primary, doesn't wipe next_event (KEV_BECOME_PRIMARY)
	    else
	      key_limit_reneg(KEV_RENEGOTIATE, *now);
	  }
      }

      // Handle data-limited keys such as Blowfish and other 64-bit block-size ciphers.
      void data_limit_add(const DataLimit::Mode mode, const size_t size)
      {
	const DataLimit::State state = data_limit->add(mode, size);
	if (state > DataLimit::None)
	  data_limit_event(mode, state);
      }

      // Handle a DataLimit event.
      void data_limit_event(const DataLimit::Mode mode, const DataLimit::State state)
      {
	OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " DATA LIMIT " << DataLimit::mode_str(mode) << ' ' << DataLimit::state_str(state) << " key_id=" << key_id_);

	// State values:
	//   DataLimit::Green -- first packet received and decrypted.
	//   DataLimit::Red -- data limit has been exceeded, so trigger a renegotiation.
	if (state == DataLimit::Red)
	  schedule_key_limit_renegotiation();

	// When we are in KEV_PRIMARY_PENDING state, we must receive at least
	// one packet from the peer on this key before we transition to
	// KEV_BECOME_PRIMARY so we can transmit on it.
	if (next_event == KEV_PRIMARY_PENDING && data_limit->is_decrypt_green())
	  set_event(KEV_NONE, KEV_BECOME_PRIMARY, *now + Time::Duration::seconds(1));
      }

      // Should we enter KEV_PRIMARY_PENDING state?  Do it if:
      // 1. we are a client,
      // 2. data limit is enabled,
      // 3. this is a renegotiated key in secondary context, i.e. not the first key, and
      // 4. no data received yet from peer on this key.
      bool data_limit_defer() const
      {
	return !proto.is_server() && data_limit && key_id_ && !data_limit->is_decrypt_green();
      }

      // General expiration set when key hits data limit threshold.
      Time data_limit_expire() const
      {
	return *now + (proto.config->handshake_window * 2);
      }

      void active_event()
      {
	set_event(KEV_ACTIVE, KEV_BECOME_PRIMARY, reached_active() + proto.config->become_primary);
      }

      void process_next_event()
      {
	if (*now >= next_event_time)
	  {
	    switch (next_event)
	      {
	      case KEV_BECOME_PRIMARY:
		if (data_limit_defer())
		  set_event(KEV_NONE, KEV_PRIMARY_PENDING, data_limit_expire());
		else
		  set_event(KEV_BECOME_PRIMARY, KEV_RENEGOTIATE, construct_time + proto.config->renegotiate);
		break;
	      case KEV_RENEGOTIATE:
	      case KEV_RENEGOTIATE_FORCE:
		prepare_expire(next_event);
		break;
	      case KEV_NEGOTIATE:
		kev_error(KEV_NEGOTIATE, Error::KEV_NEGOTIATE_ERROR);
		break;
	      case KEV_PRIMARY_PENDING:
		kev_error(KEV_PRIMARY_PENDING, Error::KEV_PENDING_ERROR);
		break;
	      case KEV_EXPIRE:
		kev_error(KEV_EXPIRE, Error::N_KEV_EXPIRE);
		break;
	      default:
		break;
	      }
	  }
      }

      void kev_error(const EventType ev, const Error::Type reason)
      {
	proto.stats->error(reason);
	invalidate(reason);
	set_event(ev);
      }

      unsigned int initial_op(const bool sender, const bool tls_crypt_v2) const
      {
	if (key_id_)
	  {
	    return CONTROL_SOFT_RESET_V1;
	  }
	else
	  {
	    if (proto.is_server() == sender)
	      return CONTROL_HARD_RESET_SERVER_V2;

	    if (!tls_crypt_v2)
	      return CONTROL_HARD_RESET_CLIENT_V2;
	    else
	      return CONTROL_HARD_RESET_CLIENT_V3;
	  }
      }

      void send_reset()
      {
	Packet pkt;
	pkt.opcode = initial_op(true, proto.tls_wrap_mode == TLS_CRYPT_V2);
	pkt.frame_prepare(*proto.config->frame, Frame::WRITE_SSL_INIT);
	raw_send(std::move(pkt));
      }

      void raw_recv(Packet&& raw_pkt)  // called by ProtoStackBase
      {
	if (raw_pkt.buf->empty() &&
	    raw_pkt.opcode == initial_op(false, proto.tls_wrap_mode == TLS_CRYPT_V2))
	  {
	    switch (state)
	      {
	      case C_WAIT_RESET:
		//send_reset(); // fixme -- possibly not needed
		set_state(C_WAIT_RESET_ACK);
		break;
	      case S_WAIT_RESET:
		send_reset();
		set_state(S_WAIT_RESET_ACK);
		break;
	      }
	  }
      }

      void app_recv(BufferPtr&& to_app_buf) // called by ProtoStackBase
      {
	app_recv_buf.put(std::move(to_app_buf));
	if (app_recv_buf.size() > APP_MSG_MAX)
	  throw proto_error("app_recv: received control message is too large");
	BufferComposed::Complete bcc = app_recv_buf.complete();
	switch (state)
	  {
	  case C_WAIT_AUTH:
	    if (recv_auth_complete(bcc))
	      {
		recv_auth(bcc.get());
		set_state(C_WAIT_AUTH_ACK);
	      }
	    break;
	  case S_WAIT_AUTH:
	    if (recv_auth_complete(bcc))
	      {
		recv_auth(bcc.get());
		send_auth();
		set_state(S_WAIT_AUTH_ACK);
	      }
	    break;
	  case S_WAIT_AUTH_ACK: // rare case where client receives auth, goes ACTIVE, but the ACK response is dropped
	  case ACTIVE:
	    if (bcc.advance_to_null()) // does composed buffer contain terminating null char?
	      proto.app_recv(key_id_, bcc.get());
	    break;
	  }
      }

      void net_send(const Packet& net_pkt, const Base::NetSendType nstype)  // called by ProtoStackBase
      {
	if (!is_reliable || nstype != Base::NET_SEND_RETRANSMIT) // retransmit packets on UDP only, not TCP
	  proto.net_send(key_id_, net_pkt);
      }

      void post_ack_action()
      {
	if (state <= LAST_ACK_STATE && !rel_send.n_unacked())
	  {
	    switch (state)
	      {
	      case C_WAIT_RESET_ACK:
		start_handshake();
		send_auth();
		set_state(C_WAIT_AUTH);
		break;
	      case S_WAIT_RESET_ACK:
		start_handshake();
		set_state(S_WAIT_AUTH);
		break;
	      case C_WAIT_AUTH_ACK:
		active();
		set_state(ACTIVE);
		break;
	      case S_WAIT_AUTH_ACK:
		active();
		set_state(ACTIVE);
		break;
	      }
	  }
      }

      void send_auth()
      {
	BufferPtr buf = new BufferAllocated();
	proto.config->frame->prepare(Frame::WRITE_SSL_CLEARTEXT, *buf);
	buf->write(proto_context_private::auth_prefix, sizeof(proto_context_private::auth_prefix));
	tlsprf->self_randomize(*proto.config->rng);
	tlsprf->self_write(*buf);
	const std::string options = proto.config->options_string();
	write_auth_string(options, *buf);
	if (!proto.is_server())
	  {
	    OPENVPN_LOG_PROTO("Tunnel Options:" << options);
	    buf->or_flags(BufferAllocated::DESTRUCT_ZERO);
	    if (proto.config->xmit_creds)
	      proto.client_auth(*buf);
	    else
	      {
		write_empty_string(*buf); // username
		write_empty_string(*buf); // password
	      }
	    const std::string peer_info = proto.config->peer_info_string();
	    write_auth_string(peer_info, *buf);
	  }
	app_send_validate(std::move(buf));
	dirty = true;
      }

      void recv_auth(BufferPtr buf)
      {
	const unsigned char *buf_pre = buf->read_alloc(sizeof(proto_context_private::auth_prefix));
	if (std::memcmp(buf_pre, proto_context_private::auth_prefix, sizeof(proto_context_private::auth_prefix)))
	  throw proto_error("bad_auth_prefix");
	tlsprf->peer_read(*buf);
	const std::string options = read_auth_string<std::string>(*buf);
	if (proto.is_server())
	  {
	    const std::string username = read_auth_string<std::string>(*buf);
	    const SafeString password = read_auth_string<SafeString>(*buf);
	    const std::string peer_info = read_auth_string<std::string>(*buf);
	    proto.server_auth(username, password, peer_info, Base::auth_cert());
	  }
      }

      // return true if complete recv_auth message is contained in buffer
      bool recv_auth_complete(BufferComplete& bc) const
      {
	if (!bc.advance(sizeof(proto_context_private::auth_prefix)))
	  return false;
	if (!tlsprf->peer_read_complete(bc))
	  return false;
	if (!bc.advance_string()) // options
	  return false;
	if (proto.is_server())
	  {
	    if (!bc.advance_string()) // username
	      return false;
	    if (!bc.advance_string()) // password
	      return false;
	    if (!bc.advance_string()) // peer_info
	      return false;
	  }
	return true;
      }

      void active()
      {
	if (proto.config->debug_level >= 1)
	  OPENVPN_LOG_SSL("SSL Handshake: " << Base::ssl_handshake_details());
	generate_session_keys();
	while (!app_pre_write_queue.empty())
	  {
	    app_send_validate(std::move(app_pre_write_queue.front()));
	    app_pre_write_queue.pop_front();
	    dirty = true;
	  }
	reached_active_time_ = *now;
	proto.slowest_handshake_.max(reached_active_time_ - construct_time);
	active_event();
      }

      // use the TLS PRF construction to exchange session keys for building
      // the data channel crypto context
      void generate_session_keys()
      {
	std::unique_ptr<DataChannelKey> dck(new DataChannelKey());
	tlsprf->generate_key_expansion(dck->key, proto.psid_self, proto.psid_peer);
	OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " KEY " << proto.mode().str() << ' ' << dck->key.render());
	tlsprf->erase();
	dck.swap(data_channel_key);
	if (!proto.dc_deferred)
	  init_data_channel();
      }

      void prepend_dest_psid_and_acks(Buffer& buf)
      {
	// if sending ACKs, prepend dest PSID
	if (!xmit_acks.empty())
	  {
	    if (proto.psid_peer.defined())
	      proto.psid_peer.prepend(buf);
	    else
	      {
		proto.stats->error(Error::CC_ERROR);
		throw proto_error("peer_psid_undef");
	      }
	  }

	// prepend ACKs for messages received from peer
	xmit_acks.prepend(buf);
      }

      bool verify_src_psid(const ProtoSessionID& src_psid)
      {
	if (proto.psid_peer.defined())
	  {
	    if (!proto.psid_peer.match(src_psid))
	      {
		proto.stats->error(Error::CC_ERROR);
		if (proto.is_tcp())
		  invalidate(Error::CC_ERROR);
		return false;
	      }
	  }
	else
	  {
	    proto.psid_peer = src_psid;
	  }
	return true;
      }

      bool verify_dest_psid(Buffer& buf)
      {
	ProtoSessionID dest_psid(buf);
	if (!proto.psid_self.match(dest_psid))
	  {
	    proto.stats->error(Error::CC_ERROR);
	    if (proto.is_tcp())
	      invalidate(Error::CC_ERROR);
	    return false;
	  }
	return true;
      }

      void gen_head_tls_auth(const unsigned int opcode, Buffer& buf)
      {
	// write tls-auth packet ID
	proto.ta_pid_send.write_next(buf, true, now->seconds_since_epoch());

	// make space for tls-auth HMAC
	buf.prepend_alloc(proto.hmac_size);

	// write source PSID
	proto.psid_self.prepend(buf);

	// write opcode
	buf.push_front(op_compose(opcode, key_id_));

	// write hmac
	proto.ta_hmac_send->ovpn_hmac_gen(buf.data(), buf.size(),
					  1 + ProtoSessionID::SIZE,
					  proto.hmac_size,
					  PacketID::size(PacketID::LONG_FORM));
      }

      void gen_head_tls_crypt(const unsigned int opcode, BufferAllocated& buf)
      {
	// in 'work' we store all the fields that are not supposed to be encrypted
	proto.config->frame->prepare(Frame::ENCRYPT_WORK, work);
	// make space for HMAC
	work.prepend_alloc(proto.hmac_size);
	// write tls-crypt packet ID
	proto.ta_pid_send.write_next(work, true, now->seconds_since_epoch());
	// write source PSID
	proto.psid_self.prepend(work);
	// write opcode
	work.push_front(op_compose(opcode, key_id_));

	// compute HMAC using header fields (from 'work') and plaintext
	// payload (from 'buf')
	proto.tls_crypt_send->hmac_gen(work.data(), TLSCryptContext::hmac_offset,
				       buf.c_data(), buf.size());

	const size_t data_offset = TLSCryptContext::hmac_offset + proto.hmac_size;

	// encrypt the content of 'buf' (packet payload) into 'work'
	const size_t decrypt_bytes = proto.tls_crypt_send->encrypt(work.c_data() + TLSCryptContext::hmac_offset,
								   work.data() + data_offset,
								   work.max_size() - data_offset,
								   buf.c_data(), buf.size());
	if (!decrypt_bytes)
	  {
	    buf.reset_size();
	    return;
	  }
	work.inc_size(decrypt_bytes);

	// append WKc to wrapped packet for tls-crypt-v2
	if ((opcode == CONTROL_HARD_RESET_CLIENT_V3)
	    && (proto.tls_wrap_mode == TLS_CRYPT_V2))
	  proto.tls_crypt_append_wkc(work);

	// 'work' now contains the complete packet ready to go. swap it with 'buf'
	buf.swap(work);
      }

      void gen_head_tls_plain(const unsigned int opcode, Buffer& buf)
      {
	// write source PSID
        proto.psid_self.prepend(buf);
	// write opcode
	buf.push_front(op_compose(opcode, key_id_));
      }

      void gen_head(const unsigned int opcode, BufferAllocated& buf)
      {
	switch (proto.tls_wrap_mode)
	  {
	    case TLS_AUTH:
	      gen_head_tls_auth(opcode, buf);
	      break;
	    case TLS_CRYPT:
	    case TLS_CRYPT_V2:
	      gen_head_tls_crypt(opcode, buf);
	      break;
	    case TLS_PLAIN:
	      gen_head_tls_plain(opcode, buf);
	      break;
	  }
      }

      void encapsulate(id_t id, Packet& pkt) // called by ProtoStackBase
      {
	BufferAllocated& buf = *pkt.buf;

	// prepend message sequence number
	ReliableAck::prepend_id(buf, id);

	// prepend dest PSID and ACKs to reply to peer
	prepend_dest_psid_and_acks(buf);

	// generate message head
	gen_head(pkt.opcode, buf);
      }

      void generate_ack(Packet& pkt) // called by ProtoStackBase
      {
	BufferAllocated& buf = *pkt.buf;

	// prepend dest PSID and ACKs to reply to peer
	prepend_dest_psid_and_acks(buf);

	gen_head(ACK_V1, buf);
      }

      bool decapsulate_post_process(Packet& pkt, ProtoSessionID& src_psid, const PacketID pid)
      {
	Buffer& recv = *pkt.buf;

	// update our last-packet-received time
	proto.update_last_received();

	// verify source PSID
	if (!verify_src_psid(src_psid))
	  return false;

	// get current time_t
	const PacketID::time_t t = now->seconds_since_epoch();
	// verify tls_auth/crypt packet ID
	const bool pid_ok = proto.ta_pid_recv.test_add(pid, t, false);

	// process ACKs sent by peer (if packet ID check failed,
	// read the ACK IDs, but don't modify the rel_send object).
	if (ReliableAck::ack(rel_send, recv, pid_ok))
	  {
	    // make sure that our own PSID is contained in packet received from peer
	    if (!verify_dest_psid (recv))
	      return false;
	  }

	// for CONTROL packets only, not ACK
	if (pkt.opcode != ACK_V1)
	  {
	    // get message sequence number
	    const id_t id = ReliableAck::read_id (recv);

	    if (pid_ok)
	      {
		// try to push message into reliable receive object
		const unsigned int rflags = rel_recv.receive (pkt, id);

		// should we ACK packet back to sender?
		if (rflags & ReliableRecv::ACK_TO_SENDER)
		  xmit_acks.push_back (id); // ACK packet to sender

		// was packet accepted by reliable receive object?
		if (rflags & ReliableRecv::IN_WINDOW)
		  {
		    proto.ta_pid_recv.test_add (pid, t, true); // remember tls_auth packet ID so that it can't be replayed
		    return true;
		  }
	      }
	    else // treat as replay
	      {
		proto.stats->error (Error::REPLAY_ERROR);
		if (pid.is_valid ())
		  xmit_acks.push_back (id); // even replayed packets must be ACKed or protocol could deadlock
	      }
	  }
	else
	  {
	    if (pid_ok)
	      proto.ta_pid_recv.test_add (pid, t, true); // remember tls_auth packet ID of ACK packet to prevent replay
	    else
	      proto.stats->error (Error::REPLAY_ERROR);
	  }
	return false;

      }

      bool decapsulate_tls_auth(Packet &pkt)
      {
	Buffer& recv = *pkt.buf;
	const unsigned char *orig_data = recv.data ();
	const size_t orig_size = recv.size ();

	// advance buffer past initial op byte
	recv.advance (1);

	// get source PSID
	ProtoSessionID src_psid (recv);

	// verify HMAC
	{
	  recv.advance (proto.hmac_size);
	  if (!proto.ta_hmac_recv->ovpn_hmac_cmp(orig_data, orig_size,
						 1 + ProtoSessionID::SIZE,
						 proto.hmac_size,
						 PacketID::size (PacketID::LONG_FORM)))
	    {
	      proto.stats->error(Error::HMAC_ERROR);
	      if (proto.is_tcp())
		invalidate(Error::HMAC_ERROR);
	      return false;
	    }
	}

	// read tls_auth packet ID
	const PacketID pid = proto.ta_pid_recv.read_next(recv);

	return decapsulate_post_process(pkt, src_psid, pid);
      }

      bool decapsulate_tls_crypt(Packet &pkt)
      {
	BufferAllocated& recv = *pkt.buf;
	const unsigned char *orig_data = recv.data();
	const size_t orig_size = recv.size();

	// advance buffer past initial op byte
	recv.advance(1);
	// get source PSID
	ProtoSessionID src_psid(recv);
	// get tls-crypt packet ID
	const PacketID pid = proto.ta_pid_recv.read_next(recv);
	// skip the hmac
	recv.advance(proto.hmac_size);

	const size_t data_offset = TLSCryptContext::hmac_offset + proto.hmac_size;
	if (orig_size < data_offset)
	  return false;

	// decrypt payload
	proto.config->frame->prepare(Frame::DECRYPT_WORK, work);

	const size_t decrypt_bytes = proto.tls_crypt_recv->decrypt(orig_data + TLSCryptContext::hmac_offset,
								   work.data(), work.max_size(),
								   recv.c_data(), recv.size());
	if (!decrypt_bytes)
	  {
	    proto.stats->error(Error::DECRYPT_ERROR);
	    if (proto.is_tcp())
	      invalidate(Error::DECRYPT_ERROR);
	    return false;
	  }

	work.inc_size(decrypt_bytes);

	// verify HMAC
	if (!proto.tls_crypt_recv->hmac_cmp(orig_data, TLSCryptContext::hmac_offset,
					    work.c_data(), work.size()))
	  {
	    proto.stats->error(Error::HMAC_ERROR);
	    if (proto.is_tcp())
	      invalidate(Error::HMAC_ERROR);
	    return false;
	  }

	// move the decrypted payload to 'recv', so that the processing of the
	// packet can continue
	recv.swap(work);

	return decapsulate_post_process(pkt, src_psid, pid);
      }

      bool decapsulate_tls_plain(Packet &pkt)
      {
	Buffer& recv = *pkt.buf;

	// update our last-packet-received time
	proto.update_last_received();

	// advance buffer past initial op byte
	recv.advance(1);

	// verify source PSID
	ProtoSessionID src_psid(recv);
	if (!verify_src_psid(src_psid))
	  return false;

	// process ACKs sent by peer
	if (ReliableAck::ack(rel_send, recv, true))
	  {
	    // make sure that our own PSID is in packet received from peer
	    if (!verify_dest_psid(recv))
	      return false;
	  }

	// for CONTROL packets only, not ACK
	if (pkt.opcode != ACK_V1)
	  {
	    // get message sequence number
	    const id_t id = ReliableAck::read_id(recv);

	    // try to push message into reliable receive object
	    const unsigned int rflags = rel_recv.receive(pkt, id);

	    // should we ACK packet back to sender?
	    if (rflags & ReliableRecv::ACK_TO_SENDER)
	      xmit_acks.push_back(id); // ACK packet to sender

	    // was packet accepted by reliable receive object?
	    if (rflags & ReliableRecv::IN_WINDOW)
	      return true;
	  }
	return false;
      }

      bool unwrap_tls_crypt_wkc(Buffer &recv)
      {
	// the ``WKc`` is located at the end of the packet, after the tls-crypt
	// payload.
	// Format is as follows (as documented by Steffan Krager):
	//
	// ``len = len(WKc)`` (16 bit, network byte order)
	// ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
	// ``IV = 128 most significant bits of T``
	// ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata) || len``

	const unsigned char *orig_data = recv.data();
	const size_t orig_size = recv.size();
	const size_t hmac_size = proto.config->tls_crypt_context->digest_size();
	const size_t tls_frame_size = 1 + ProtoSessionID::SIZE +
				      PacketID::size(PacketID::LONG_FORM) +
				      hmac_size +
				      // the following is the tls-crypt payload
				      sizeof(char) +  // length of ACK array
				      sizeof(id_t); // reliable ID

	// check that at least the authentication tag ``T`` is present
	if (orig_size < (tls_frame_size + hmac_size))
	  return false;

	// the ``WKc`` is just appended after the standard tls-crypt frame
	const unsigned char *wkc_raw = orig_data + tls_frame_size;
	const size_t wkc_raw_size = orig_size - tls_frame_size - sizeof(uint16_t);
	// retrieve the ``WKc`` len from the bottom of the packet and convert it to Host Order
	uint16_t wkc_len = ntohs(*(uint16_t *)(wkc_raw + wkc_raw_size));
	// length sanity check (the size of the ``len`` field is included in the value)
	if ((wkc_len - sizeof(uint16_t)) != wkc_raw_size)
	  return false;

	BufferAllocated plaintext(wkc_len, BufferAllocated::CONSTRUCT_ZERO);
	// plaintext will be used to compute the Auth Tag, therefore start by prepnding
	// the WKc length in network order
	wkc_len = htons(wkc_len);
	plaintext.write(&wkc_len, sizeof(wkc_len));
	const size_t decrypt_bytes = proto.tls_crypt_server->decrypt(wkc_raw,
								     plaintext.data() + 2,
								     plaintext.max_size() - 2,
								     wkc_raw + hmac_size,
								     wkc_raw_size - hmac_size);
	plaintext.inc_size(decrypt_bytes);
	// decrypted data must at least contain a full 2048bits client key
	// (metadata is optional)
	if (plaintext.size() < OpenVPNStaticKey::KEY_SIZE)
	  {
	    proto.stats->error(Error::DECRYPT_ERROR);
	    if (proto.is_tcp())
	      invalidate(Error::DECRYPT_ERROR);
	    return false;
	  }

	if (!proto.tls_crypt_server->hmac_cmp(wkc_raw, 0,
					      plaintext.c_data(),
					      plaintext.size()))
	  {
	    proto.stats->error(Error::HMAC_ERROR);
	    if (proto.is_tcp())
	      invalidate(Error::HMAC_ERROR);
	    return false;
	  }

	// we can now remove the WKc length from the plaintext, as it is not
	// really part of the key material
	plaintext.advance(sizeof(wkc_len));

	// WKc has been authenticated: it contains the client key followed
	// by the optional metadata. Let's initialize the tls-crypt context
	// with the client key

	OpenVPNStaticKey client_key;
	plaintext.read(client_key.raw_alloc(), OpenVPNStaticKey::KEY_SIZE);
	proto.reset_tls_crypt(*proto.config, client_key);

	// verify metadata
	int metadata_type = -1;
	if (!plaintext.empty())
	  metadata_type = plaintext.pop_front();

	if (!proto.tls_crypt_metadata->verify(metadata_type, plaintext))
	  {
	    proto.stats->error(Error::TLS_CRYPT_META_FAIL);
	    return false;
	  }

	// virtually remove the WKc from the packet
	recv.set_size(tls_frame_size);

	return true;
      }

      bool decapsulate(Packet& pkt) // called by ProtoStackBase
      {
	try {
	  switch (proto.tls_wrap_mode)
	  {
	    case TLS_AUTH:
	      return decapsulate_tls_auth(pkt);
	    case TLS_CRYPT_V2:
	      if (pkt.opcode == CONTROL_HARD_RESET_CLIENT_V3)
		{
		  // unwrap WKc and extract Kc (client key) from packet.
		  // This way we can initialize the tls-crypt per-client contexts
		  // (this happens on the server side only)
		  if (!unwrap_tls_crypt_wkc(*pkt.buf))
		  {
		    return false;
		  }
		}
	      // now that the tls-crypt contexts have been initialized it is
	      // possible to proceed with the standard tls-crypt decapsulation
	      /* no break */
	    case TLS_CRYPT:
	      return decapsulate_tls_crypt(pkt);
	    case TLS_PLAIN:
	      return decapsulate_tls_plain(pkt);
	    }
	}
	catch (BufferException&)
	  {
	    proto.stats->error(Error::BUFFER_ERROR);
	    if (proto.is_tcp())
	      invalidate(Error::BUFFER_ERROR);
	  }
	return false;
      }

      // for debugging
      static const char *state_string(const int s)
      {
	switch (s)
	  {
	  case C_WAIT_RESET_ACK:
	    return "C_WAIT_RESET_ACK";
	  case C_WAIT_AUTH_ACK:
	    return "C_WAIT_AUTH_ACK";
	  case S_WAIT_RESET_ACK:
	    return "S_WAIT_RESET_ACK";
	  case S_WAIT_AUTH_ACK:
	    return "S_WAIT_AUTH_ACK";
	  case C_INITIAL:
	    return "C_INITIAL";
	  case C_WAIT_RESET:
	    return "C_WAIT_RESET";
	  case C_WAIT_AUTH:
	    return "C_WAIT_AUTH";
	  case S_INITIAL:
	    return "S_INITIAL";
	  case S_WAIT_RESET:
	    return "S_WAIT_RESET";
	  case S_WAIT_AUTH:
	    return "S_WAIT_AUTH";
	  case ACTIVE:
	    return "ACTIVE";
	  default:
	    return "STATE_UNDEF";
	  }
      }

      // for debugging
      int seconds_until(const Time& next_time)
      {
	Time::Duration d = next_time - *now;
	if (d.is_infinite())
	  return -1;
	else
	  return d.to_seconds();
      }

      // BEGIN KeyContext data members

      ProtoContext& proto; // parent
      int state;
      unsigned int key_id_;
      unsigned int crypto_flags;
      int remote_peer_id; // -1 to disable
      bool enable_op32;
      bool dirty;
      bool key_limit_renegotiation_fired;
      bool is_reliable;
      Compress::Ptr compress;
      CryptoDCInstance::Ptr crypto;
      TLSPRFInstance::Ptr tlsprf;
      Time construct_time;
      Time reached_active_time_;
      Time next_event_time;
      EventType current_event;
      EventType next_event;
      std::deque<BufferPtr> app_pre_write_queue;
      std::unique_ptr<DataChannelKey> data_channel_key;
      BufferComposed app_recv_buf;
      std::unique_ptr<DataLimit> data_limit;
      BufferAllocated work;

      // static member used by validate_tls_crypt()
      static BufferAllocated static_work;
    };

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

      virtual bool validate(const BufferAllocated& net_buf) = 0;
    };

    // Validate the integrity of a packet, only considering tls-auth HMAC.
    class TLSAuthPreValidate : public TLSWrapPreValidate
    {
    public:
      OPENVPN_SIMPLE_EXCEPTION(tls_auth_pre_validate);

      TLSAuthPreValidate(const Config& c, const bool server)
      {
	if (!c.tls_auth_enabled())
	  throw tls_auth_pre_validate();

	// save hard reset op we expect to receive from peer
	reset_op = server ? CONTROL_HARD_RESET_CLIENT_V2 : CONTROL_HARD_RESET_SERVER_V2;

	// init OvpnHMACInstance
	ta_hmac_recv = c.tls_auth_context->new_obj();

	// init tls_auth hmac
	if (c.key_direction >= 0)
	  {
	    // key-direction is 0 or 1
	    const unsigned int key_dir = c.key_direction ? OpenVPNStaticKey::INVERSE : OpenVPNStaticKey::NORMAL;
	    ta_hmac_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::DECRYPT | key_dir));
	  }
	else
	  {
	    // key-direction bidirectional mode
	    ta_hmac_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC));
	  }
      }

      bool validate(const BufferAllocated& net_buf)
      {
	try
	{
	    if (!net_buf.size())
	      return false;

	    const unsigned int op = net_buf[0];
	    if (opcode_extract(op) != reset_op || key_id_extract(op) != 0)
	      return false;

	    return ta_hmac_recv->ovpn_hmac_cmp(net_buf.c_data(), net_buf.size(),
					       1 + ProtoSessionID::SIZE,
					       ta_hmac_recv->output_size(),
					       PacketID::size(PacketID::LONG_FORM));
	}
	catch (BufferException&)
	{
	}

	return false;
      }

    private:
      OvpnHMACInstance::Ptr ta_hmac_recv;
      unsigned int reset_op;
    };

    class TLSCryptPreValidate : public TLSWrapPreValidate
    {
    public:
      OPENVPN_SIMPLE_EXCEPTION(tls_crypt_pre_validate);

      TLSCryptPreValidate(const Config& c, const bool server)
      {
	if (!c.tls_crypt_enabled())
	  throw tls_crypt_pre_validate();

	// save hard reset op we expect to receive from peer
	reset_op = server ? CONTROL_HARD_RESET_CLIENT_V2 : CONTROL_HARD_RESET_SERVER_V2;

	tls_crypt_recv = c.tls_crypt_context->new_obj_recv();

	// static direction assignment - not user configurable
	const unsigned int key_dir = server ? OpenVPNStaticKey::NORMAL : OpenVPNStaticKey::INVERSE;
	tls_crypt_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::DECRYPT | key_dir),
			     c.tls_key.slice(OpenVPNStaticKey::CIPHER | OpenVPNStaticKey::DECRYPT | key_dir));

	// needed to create the decrypt buffer during validation
	frame = c.frame;
      }

      bool validate(const BufferAllocated& net_buf)
      {
	try
	{
	    if (!net_buf.size())
	      return false;

	    const unsigned int op = net_buf[0];
	    if (opcode_extract(op) != reset_op || key_id_extract(op) != 0)
	      return false;

	    const size_t data_offset = TLSCryptContext::hmac_offset + tls_crypt_recv->output_hmac_size();
	    if (net_buf.size() < data_offset)
	      return false;

	    frame->prepare(Frame::DECRYPT_WORK, work);

	    // decrypt payload from 'net_buf' into 'work'
	    const size_t decrypt_bytes = tls_crypt_recv->decrypt(net_buf.c_data() + TLSCryptContext::hmac_offset,
								 work.data(), work.max_size(),
								 net_buf.c_data() + data_offset,
								 net_buf.size() - data_offset);
	    if (!decrypt_bytes)
	      return false;

	    work.inc_size(decrypt_bytes);

	    // verify HMAC
	    return tls_crypt_recv->hmac_cmp(net_buf.c_data(),
					    TLSCryptContext::hmac_offset,
					    work.data(), work.size());
	}
	catch (BufferException&)
	{
	}
	return false;
      }

    protected:
      unsigned int reset_op;

    private:
      TLSCryptInstance::Ptr tls_crypt_recv;
      Frame::Ptr frame;
      BufferAllocated work;
    };

    class TLSCryptV2PreValidate : public TLSCryptPreValidate
    {
        public:
          OPENVPN_SIMPLE_EXCEPTION(tls_crypt_v2_pre_validate);

          TLSCryptV2PreValidate(const Config& c, const bool server)
	    : TLSCryptPreValidate(c, server)
          {
            if (!c.tls_crypt_v2_enabled())
              throw tls_crypt_v2_pre_validate();

            // in case of server peer, we expect the new v3 packet type
            if (server)
              reset_op = CONTROL_HARD_RESET_CLIENT_V3;
          }
    };

    OPENVPN_SIMPLE_EXCEPTION(select_key_context_error);

    ProtoContext(const Config::Ptr& config_arg,             // configuration
		 const SessionStats::Ptr& stats_arg)        // error stats
      : config(config_arg),
	stats(stats_arg),
	mode_(config_arg->ssl_factory->mode()),
	n_key_ids(0),
	now_(config_arg->now)
    {
      const Config& c = *config;

      // tls-auth setup
      if (c.tls_crypt_v2_enabled())
	{
	  tls_wrap_mode = TLS_CRYPT_V2;

	  // get HMAC size from Digest object
	  hmac_size = c.tls_crypt_context->digest_size();
	}
      else if (c.tls_crypt_enabled())
	{
	  tls_wrap_mode = TLS_CRYPT;

	  // get HMAC size from Digest object
	  hmac_size = c.tls_crypt_context->digest_size();
	}
      else if (c.tls_auth_enabled())
	{
	  tls_wrap_mode = TLS_AUTH;

	  // get HMAC size from Digest object
	  hmac_size = c.tls_auth_context->size();
	}
      else
	{
	  tls_wrap_mode = TLS_PLAIN;
	  hmac_size = 0;
	}
    }

    uint32_t get_tls_warnings() const
    {
      if (primary)
	return primary->get_tls_warnings();

      OPENVPN_LOG("TLS: primary key context uninitialized. Can't retrieve TLS warnings");
      return 0;
    }

    void reset_tls_crypt(const Config& c, const OpenVPNStaticKey& key)
    {
      tls_crypt_send = c.tls_crypt_context->new_obj_send();
      tls_crypt_recv = c.tls_crypt_context->new_obj_recv();

      // static direction assignment - not user configurable
      unsigned int key_dir = is_server() ?
			     OpenVPNStaticKey::NORMAL :
			     OpenVPNStaticKey::INVERSE;

      tls_crypt_send->init(key.slice(OpenVPNStaticKey::HMAC |
				     OpenVPNStaticKey::ENCRYPT | key_dir),
			   key.slice(OpenVPNStaticKey::CIPHER |
				     OpenVPNStaticKey::ENCRYPT | key_dir));
      tls_crypt_recv->init(key.slice(OpenVPNStaticKey::HMAC |
				     OpenVPNStaticKey::DECRYPT | key_dir),
			   key.slice(OpenVPNStaticKey::CIPHER |
				     OpenVPNStaticKey::DECRYPT | key_dir));
    }

    void reset_tls_crypt_server(const Config& c)
    {
      //tls-crypt session key is derived later from WKc received from the client
      tls_crypt_send.reset();
      tls_crypt_recv.reset();

      //server context is used only to process incoming WKc's
      tls_crypt_server = c.tls_crypt_context->new_obj_recv();

      //the server key is composed by one key set only, therefore direction and
      //mode should not be specified when slicing
      tls_crypt_server->init(c.tls_key.slice(OpenVPNStaticKey::HMAC),
			     c.tls_key.slice(OpenVPNStaticKey::CIPHER));

      tls_crypt_metadata = c.tls_crypt_metadata_factory->new_obj();
    }

    void reset()
    {
      const Config& c = *config;

      // defer data channel initialization until after client options pull?
      dc_deferred = c.dc_deferred;

      // clear key contexts
      reset_all();

      // start with key ID 0
      upcoming_key_id = 0;

      unsigned int key_dir;

      // tls-auth initialization
      switch (tls_wrap_mode)
	{
	  case TLS_CRYPT:
	    reset_tls_crypt(c, c.tls_key);
	    // init tls_crypt packet ID
	    ta_pid_send.init(PacketID::LONG_FORM);
	    ta_pid_recv.init(c.pid_mode, PacketID::LONG_FORM, "SSL-CC", 0, stats);
	    break;
	  case TLS_CRYPT_V2:
	    if (is_server())
	      // setup key to be used to unwrap WKc upon client connection.
	      // tls-crypt session key setup is postponed to reception of WKc
	      // from client
	      reset_tls_crypt_server(c);
	    else
	      reset_tls_crypt(c, c.tls_key);
	    // init tls_crypt packet ID
	    ta_pid_send.init(PacketID::LONG_FORM);
	    ta_pid_recv.init(c.pid_mode, PacketID::LONG_FORM, "SSL-CC", 0, stats);
	    break;
	  case TLS_AUTH:
	    // init OvpnHMACInstance
	    ta_hmac_send = c.tls_auth_context->new_obj();
	    ta_hmac_recv = c.tls_auth_context->new_obj();

	    // init tls_auth hmac
	    if (c.key_direction >= 0)
	      {
	        // key-direction is 0 or 1
	        key_dir = c.key_direction ? OpenVPNStaticKey::INVERSE : OpenVPNStaticKey::NORMAL;
	        ta_hmac_send->init(c.tls_key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::ENCRYPT | key_dir));
	        ta_hmac_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::DECRYPT | key_dir));
	      }
	    else
	      {
	        // key-direction bidirectional mode
	        ta_hmac_send->init(c.tls_key.slice(OpenVPNStaticKey::HMAC));
	        ta_hmac_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC));
	      }

	    // init tls_auth packet ID
	    ta_pid_send.init(PacketID::LONG_FORM);
	    ta_pid_recv.init(c.pid_mode, PacketID::LONG_FORM, "SSL-CC", 0, stats);
	    break;
	  case TLS_PLAIN:
	    break;
      }

      // initialize proto session ID
      psid_self.randomize(*c.prng);
      psid_peer.reset();

      // initialize key contexts
      primary.reset(new KeyContext(*this, is_client()));
      OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " New KeyContext PRIMARY id=" << primary->key_id());

      // initialize keepalive timers
      keepalive_expire = Time::infinite();   // initially disabled
      update_last_sent();                    // set timer for initial keepalive send
    }

    void set_protocol(const Protocol& p)
    {
      config->set_protocol(p);
      if (primary)
	primary->set_protocol(p);
      if (secondary)
	secondary->set_protocol(p);
    }

    // Free up space when parent object has been halted but
    // object destruction is not immediately scheduled.
    void pre_destroy()
    {
      reset_all();
    }

    // Is primary key defined
    bool primary_defined()
    {
      return bool(primary);
    }

    virtual ~ProtoContext() {}

    // return the PacketType of an incoming network packet
    PacketType packet_type(const Buffer& buf)
    {
      return PacketType(buf, *this);
    }

    // start protocol negotiation
    void start()
    {
      if (!primary)
	throw proto_error("start: no primary key");
      primary->start();
      update_last_received(); // set an upper bound on when we expect a response
    }

    // trigger a protocol renegotiation
    void renegotiate()
    {
      // initialize secondary key context
      new_secondary_key(true);
      secondary->start();
    }

    // Should be called at the end of sequence of send/recv
    // operations on underlying protocol object.
    // If control_channel is true, do a full flush.
    // If control_channel is false, optimize flush for data
    // channel only.
    void flush(const bool control_channel)
    {
      if (control_channel || process_events())
	{
	  do {
	    if (primary)
	      primary->flush();
	    if (secondary)
	      secondary->flush();
	  } while (process_events());
	}
    }

    // Perform various time-based housekeeping tasks such as retransmiting
    // unacknowleged packets as part of the reliability layer and testing
    // for keepalive timouts.
    // Should be called at the time returned by next_housekeeping.
    void housekeeping()
    {
      // handle control channel retransmissions on primary
      if (primary)
	primary->retransmit();

      // handle control channel retransmissions on secondary
      if (secondary)
	secondary->retransmit();

      // handle possible events
      flush(false);

      // handle keepalive/expiration
      keepalive_housekeeping();
    }

    // When should we next call housekeeping?
    // Will return a time value for immediate execution
    // if session has been invalidated.
    Time next_housekeeping() const
    {
      if (!invalidated())
	{
	  Time ret = Time::infinite();
	  if (primary)
	    ret.min(primary->next_retransmit());
	  if (secondary)
	    ret.min(secondary->next_retransmit());
	  ret.min(keepalive_xmit);
	  ret.min(keepalive_expire);
	  return ret;
	}
      else
	return Time();
    }

    // send app-level cleartext to remote peer

    void control_send(BufferPtr&& app_bp)
    {
      select_control_send_context().app_send(std::move(app_bp));
    }

    void control_send(BufferAllocated&& app_buf)
    {
      control_send(app_buf.move_to_ptr());
    }

    // validate a control channel network packet
    bool control_net_validate(const PacketType& type, const Buffer& net_buf)
    {
      return type.is_defined() && KeyContext::validate(net_buf, *this, now_);
    }

    // pass received control channel network packets (ciphertext) into protocol object

    bool control_net_recv(const PacketType& type, BufferAllocated&& net_buf)
    {
      Packet pkt(net_buf.move_to_ptr(), type.opcode);
      if (type.is_soft_reset() && !renegotiate_request(pkt))
	return false;
      return select_key_context(type, true).net_recv(std::move(pkt));
    }

    bool control_net_recv(const PacketType& type, BufferPtr&& net_bp)
    {
      Packet pkt(std::move(net_bp), type.opcode);
      if (type.is_soft_reset() && !renegotiate_request(pkt))
	return false;
      return select_key_context(type, true).net_recv(std::move(pkt));
    }

    // encrypt a data channel packet using primary KeyContext
    void data_encrypt(BufferAllocated& in_out)
    {
      //OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " DATA ENCRYPT size=" << in_out.size());
      if (!primary)
	throw proto_error("data_encrypt: no primary key");
      primary->encrypt(in_out);
    }

    // decrypt a data channel packet (automatically select primary
    // or secondary KeyContext based on packet content)
    bool data_decrypt(const PacketType& type, BufferAllocated& in_out)
    {
      bool ret = false;

      //OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " DATA DECRYPT key_id=" << select_key_context(type, false).key_id() << " size=" << in_out.size());

      select_key_context(type, false).decrypt(in_out);

      // update time of most recent packet received
      if (in_out.size())
	{
	  update_last_received();
	  ret = true;
	}

      // discard keepalive packets
      if (proto_context_private::is_keepalive(in_out))
	{
	  in_out.reset_size();
	}

      return ret;
    }

    // enter disconnected state
    void disconnect(const Error::Type reason)
    {
      if (primary)
	primary->invalidate(reason);
      if (secondary)
	secondary->invalidate(reason);
    }

    // normally used by UDP clients to tell the server that
    // they are disconnecting
    void send_explicit_exit_notify()
    {
      if (is_client() && is_udp() && primary)
	primary->send_explicit_exit_notify();
    }

    // should be called after a successful network packet transmit
    void update_last_sent()
    {
      keepalive_xmit = *now_ + config->keepalive_ping;
    }

    // can we call data_encrypt or data_decrypt yet?
    bool data_channel_ready() const { return primary && primary->data_channel_ready(); }

    // total number of SSL/TLS negotiations during lifetime of ProtoContext object
    unsigned int negotiations() const { return n_key_ids; }

    // worst-case handshake time
    const Time::Duration& slowest_handshake() { return slowest_handshake_; }

    // was primary context invalidated by an exception?
    bool invalidated() const { return primary && primary->invalidated(); }

    // reason for invalidation if invalidated() above returns true
    Error::Type invalidation_reason() const { return primary->invalidation_reason(); }

    // Do late initialization of data channel, for example
    // on client after server push, or on server after client
    // capabilities are known.
    void init_data_channel()
    {
      dc_deferred = false;

      // initialize data channel (crypto & compression)
      if (primary)
	primary->init_data_channel();
      if (secondary)
	secondary->init_data_channel();
    }

    // Call on client with server-pushed options
    void process_push(const OptionList& opt, const ProtoContextOptions& pco)
    {
      // modify config with pushed options
      config->process_push(opt, pco);

      // in case keepalive parms were modified by push
      keepalive_parms_modified();
    }

    // Return the current transport alignment adjustment
    size_t align_adjust_hint() const
    {
      return config->enable_op32 ? 0 : 1;
    }

    // Return true if keepalive parameter(s) are enabled
    bool is_keepalive_enabled() const
    {
      return config->keepalive_ping.enabled()
	  || config->keepalive_timeout.enabled();
    }

    // Disable keepalive for rest of session,
    // but return the previous keepalive parameters.
    void disable_keepalive(unsigned int& keepalive_ping,
			   unsigned int& keepalive_timeout)
    {
      keepalive_ping = config->keepalive_ping.enabled() ? config->keepalive_ping.to_seconds() : 0;
      keepalive_timeout = config->keepalive_timeout.enabled() ? config->keepalive_timeout.to_seconds() : 0;
      config->keepalive_ping = Time::Duration::infinite();
      config->keepalive_timeout = Time::Duration::infinite();
      keepalive_parms_modified();
    }

    // Notify our component KeyContext when per-key Data Limits have been reached
    void data_limit_notify(const int key_id,
			   const DataLimit::Mode cdl_mode,
			   const DataLimit::State cdl_status)
    {
      if (primary && key_id == primary->key_id())
	primary->data_limit_notify(cdl_mode, cdl_status);
      else if (secondary && key_id == secondary->key_id())
	secondary->data_limit_notify(cdl_mode, cdl_status);
    }

    // access the data channel settings
    CryptoDCSettings& dc_settings()
    {
      return config->dc;
    }

    // reset the data channel factory
    void reset_dc_factory()
    {
      config->dc.reset();
    }

    // set the local peer ID (or -1 to disable)
    void set_local_peer_id(const int local_peer_id)
    {
      config->local_peer_id = local_peer_id;
    }

    // current time
    const Time& now() const { return *now_; }
    void update_now() { now_->update(); }

    // frame
    const Frame& frame() const { return *config->frame; }
    const Frame::Ptr& frameptr() const { return config->frame; }

    // client or server?
    const Mode& mode() const { return mode_; }
    bool is_server() const { return mode_.is_server(); }
    bool is_client() const { return mode_.is_client(); }

    // tcp/udp mode
    bool is_tcp() { return config->protocol.is_tcp(); }
    bool is_udp() { return config->protocol.is_udp(); }

    // configuration
    const Config& conf() const { return *config; }
    Config& conf() { return *config; }
    Config::Ptr conf_ptr() const { return config; }

    // stats
    SessionStats& stat() const { return *stats; }

  private:

    // TLS wrapping mode for the control channel
    enum TLSWrapMode {
      TLS_PLAIN,
      TLS_AUTH,
      TLS_CRYPT,
      TLS_CRYPT_V2
    };

    void reset_all()
    {
      if (primary)
	primary->rekey(CryptoDCInstance::DEACTIVATE_ALL);
      primary.reset();
      secondary.reset();
    }

    virtual void control_net_send(const Buffer& net_buf) = 0;

    // app may take ownership of app_bp via std::move
    virtual void control_recv(BufferPtr&& app_bp) = 0;

    // Called on client to request username/password credentials.
    // Should be overriden by derived class if credentials are required.
    // username and password should be written into buf with write_auth_string().
    virtual void client_auth(Buffer& buf)
    {
      write_empty_string(buf); // username
      write_empty_string(buf); // password
    }

    // Called on server with credentials and peer info provided by client.
    // Should be overriden by derived class if credentials are required.
    virtual void server_auth(const std::string& username,
			     const SafeString& password,
			     const std::string& peer_info,
			     const AuthCert::Ptr& auth_cert)
    {
    }

    // Called when initial KeyContext transitions to ACTIVE state
    virtual void active()
    {
    }

    void update_last_received()
    {
      keepalive_expire = *now_ + config->keepalive_timeout;
    }

    void net_send(const unsigned int key_id, const Packet& net_pkt)
    {
      control_net_send(net_pkt.buffer());
    }

    void app_recv(const unsigned int key_id, BufferPtr&& to_app_buf)
    {
      control_recv(std::move(to_app_buf));
    }

    // we're getting a request from peer to renegotiate.
    bool renegotiate_request(Packet& pkt)
    {
      if (KeyContext::validate(pkt.buffer(), *this, now_))
	{
	  new_secondary_key(false);
	  return true;
	}
      else
	return false;
    }

    // select a KeyContext (primary or secondary) for received network packets
    KeyContext& select_key_context(const PacketType& type, const bool control)
    {
      const unsigned int flags = type.flags & (PacketType::DEFINED|PacketType::SECONDARY|PacketType::CONTROL);
      if (!control)
	{
	  if (flags == (PacketType::DEFINED) && primary)
	    return *primary;
	  else if (flags == (PacketType::DEFINED|PacketType::SECONDARY) && secondary)
	    return *secondary;
	}
      else
	{
	  if (flags == (PacketType::DEFINED|PacketType::CONTROL) && primary)
	    return *primary;
	  else if (flags == (PacketType::DEFINED|PacketType::SECONDARY|PacketType::CONTROL) && secondary)
	    return *secondary;
	}
      throw select_key_context_error();
    }

    // Select a KeyContext (primary or secondary) for control channel sends.
    // Even after new key context goes active, we still wait for
    // KEV_BECOME_PRIMARY event (controlled by the become_primary duration
    // in Config) before we use it for app-level control-channel
    // transmissions.  Simulations have found this method to be more reliable
    // than the immediate rollover practiced by OpenVPN 2.x.
    KeyContext& select_control_send_context()
    {
      OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " CONTROL SEND");
      if (!primary)
	throw proto_error("select_control_send_context: no primary key");
      return *primary;
    }

    // Possibly send a keepalive message, and check for expiration
    // of session due to lack of received packets from peer.
    void keepalive_housekeeping()
    {
      const Time now = *now_;

      // check for keepalive timeouts
      if (now >= keepalive_xmit && primary)
	{
	  primary->send_keepalive();
	  update_last_sent();
	}
      if (now >= keepalive_expire)
	{
	  // no contact with peer, disconnect
	  stats->error(Error::KEEPALIVE_TIMEOUT);
	  disconnect(Error::KEEPALIVE_TIMEOUT);
	}
    }

    // Process KEV_x events
    // Return true if any events were processed.
    bool process_events()
    {
      bool did_work = false;

      // primary
      if (primary && primary->event_pending())
	{
	  process_primary_event();
	  did_work = true;
	}

      // secondary
      if (secondary && secondary->event_pending())
	{
	  process_secondary_event();
	  did_work = true;
	}

      return did_work;
    }

    // Create a new secondary key.
    // initiator --
    //   false : remote renegotiation request
    //   true  : local renegotiation request
    void new_secondary_key(const bool initiator)
    {
      // Create the secondary
      secondary.reset(new KeyContext(*this, initiator));
      OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " New KeyContext SECONDARY id=" << secondary->key_id() << (initiator ? " local-triggered" : " remote-triggered"));
    }

    // Promote a newly renegotiated KeyContext to primary status.
    // This is usually triggered by become_primary variable (Time::Duration)
    // in Config.
    void promote_secondary_to_primary()
    {
      primary.swap(secondary);
      if (primary)
	primary->rekey(CryptoDCInstance::PRIMARY_SECONDARY_SWAP);
      if (secondary)
	secondary->prepare_expire();
      OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " PRIMARY_SECONDARY_SWAP");
    }

    void process_primary_event()
    {
      const KeyContext::EventType ev = primary->get_event();
      if (ev != KeyContext::KEV_NONE)
	{
	  primary->reset_event();
	  switch (ev)
	    {
	    case KeyContext::KEV_ACTIVE:
	      OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " SESSION_ACTIVE");
	      primary->rekey(CryptoDCInstance::ACTIVATE_PRIMARY);
	      active();
	      break;
	    case KeyContext::KEV_RENEGOTIATE:
	    case KeyContext::KEV_RENEGOTIATE_FORCE:
	      renegotiate();
	      break;
	    case KeyContext::KEV_EXPIRE:
	      if (secondary && !secondary->invalidated())
		promote_secondary_to_primary();
	      else
		{
		  stats->error(Error::PRIMARY_EXPIRE);
		  disconnect(Error::PRIMARY_EXPIRE); // primary context expired and no secondary context available
		}
	      break;
	    case KeyContext::KEV_NEGOTIATE:
	      stats->error(Error::HANDSHAKE_TIMEOUT);
	      disconnect(Error::HANDSHAKE_TIMEOUT);   // primary negotiation failed
	      break;
	    default:
	      break;
	    }
	}
      primary->set_next_event_if_unspecified();
    }

    void process_secondary_event()
    {
      const KeyContext::EventType ev = secondary->get_event();
      if (ev != KeyContext::KEV_NONE)
	{
	  secondary->reset_event();
	  switch (ev)
	    {
	    case KeyContext::KEV_ACTIVE:
	      secondary->rekey(CryptoDCInstance::NEW_SECONDARY);
	      if (primary)
		primary->prepare_expire();
	      break;
	    case KeyContext::KEV_BECOME_PRIMARY:
	      if (!secondary->invalidated())
		promote_secondary_to_primary();
	      break;
	    case KeyContext::KEV_EXPIRE:
	      secondary->rekey(CryptoDCInstance::DEACTIVATE_SECONDARY);
	      secondary.reset();
	      break;
	    case KeyContext::KEV_RENEGOTIATE_QUEUE:
	      if (primary)
		primary->key_limit_reneg(KeyContext::KEV_RENEGOTIATE_FORCE, secondary->become_primary_time());
	      break;
	    case KeyContext::KEV_NEGOTIATE:
	      stats->error(Error::HANDSHAKE_TIMEOUT);
	    case KeyContext::KEV_PRIMARY_PENDING:
	    case KeyContext::KEV_RENEGOTIATE_FORCE:
	      renegotiate();
	      break;
	    default:
	      break;
	    }
	}
      if (secondary)
	secondary->set_next_event_if_unspecified();
    }

    std::string debug_prefix()
    {
      std::string ret = openvpn::to_string(now_->raw());
      ret += is_server() ? " SERVER[" : " CLIENT[";
      if (primary)
	ret += openvpn::to_string(primary->key_id());
      if (secondary)
	{
	  ret += '/';
	  ret += openvpn::to_string(secondary->key_id());
	}
      ret += ']';
      return ret;
    }

    // key_id starts at 0, increments to KEY_ID_MASK, then recycles back to 1.
    // Therefore, if key_id is 0, it is the first key.
    unsigned int next_key_id()
    {
      ++n_key_ids;
      unsigned int ret = upcoming_key_id;
      if ((upcoming_key_id = (upcoming_key_id + 1) & KEY_ID_MASK) == 0)
	upcoming_key_id = 1;
      return ret;
    }

    // call whenever keepalive parms are modified,
    // to reset timers
    void keepalive_parms_modified()
    {
      update_last_received();

      // For keepalive_xmit timer, don't reschedule current cycle
      // unless it would fire earlier.  Subsequent cycles will
      // time according to new keepalive_ping value.
      const Time kx = *now_ + config->keepalive_ping;
      if (kx < keepalive_xmit)
	keepalive_xmit = kx;
    }

    void tls_crypt_append_wkc(BufferAllocated& dst)
    {
      if (!config->wkc.defined())
	throw proto_error("Client Key Wrapper undefined");
      dst.append(config->wkc);
    }

    // BEGIN ProtoContext data members

    Config::Ptr config;
    SessionStats::Ptr stats;

    size_t hmac_size;
    TLSWrapMode tls_wrap_mode;
    Mode mode_;                        // client or server
    unsigned int upcoming_key_id;
    unsigned int n_key_ids;

    TimePtr now_;                      // pointer to current time (a clone of config->now)
    Time keepalive_xmit;               // time in future when we will transmit a keepalive (subject to continuous change)
    Time keepalive_expire;             // time in future when we must have received a packet from peer or we will timeout session

    Time::Duration slowest_handshake_; // longest time to reach a successful handshake

    OvpnHMACInstance::Ptr ta_hmac_send;
    OvpnHMACInstance::Ptr ta_hmac_recv;

    TLSCryptInstance::Ptr tls_crypt_send;
    TLSCryptInstance::Ptr tls_crypt_recv;

    TLSCryptInstance::Ptr tls_crypt_server;
    TLSCryptMetadata::Ptr tls_crypt_metadata;

    PacketIDSend ta_pid_send;
    PacketIDReceive ta_pid_recv;

    ProtoSessionID psid_self;
    ProtoSessionID psid_peer;

    KeyContext::Ptr primary;
    KeyContext::Ptr secondary;
    bool dc_deferred;

    // END ProtoContext data members
  };

} // namespace openvpn

#endif //OPENVPN_SSL_PROTO_H
