// ---------------------------------------------------------------------------
// - Pkdhe.cpp                                                               -
// - afnix:tls service - public key cryptographic standard implementation    -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - 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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2023 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Pkdhe.hpp"
#include "Vector.hpp"
#include "Evaluable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "AsnBuffer.hpp"
#include "AsnInteger.hpp"
#include "AsnSequence.hpp"
#include "transient.tcc"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // get the sequence node from the asn buffer
  static AsnSequence* pkdhe_topseq (const AsnBuffer& abuf) {
    // map the buffer to a node
    AsnNode* node = abuf.mapnode ();
    // check for a sequence
    AsnSequence* pseq = dynamic_cast <AsnSequence*> (node);
    if (pseq == nullptr) {
      delete node;
      throw Exception ("pkdhe-error", "cannot map pkdhe sequence node");
    }
    // check the sequence length
    long nlen = pseq->getnlen ();
    if ((nlen != 3) && (nlen != 5) && (nlen != 6)) {
      delete node;
      throw Exception ("pkdhe-error", "invalid pkdhe sequence length");
    }
    return pseq;
  }

  // get the sequence relatif number by index
  static Relatif pkdhe_torval (const AsnSequence* pseq, const long index) {
    // check for nil
    if (pseq == nullptr) {
      throw Exception ("pkdhe-error", "invalid nil pkdhe sequence node");
    }
    // get the node by index
    auto inod = dynamic_cast <AsnInteger*> (pseq->getnode (index));
    if (inod == nullptr) {
      throw Exception ("pkdhe-error", "cannot map integer node");
    }
    return inod->torelatif ();
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a default key
  
  Pkdhe::Pkdhe (void) {
    p_pkey = nullptr;
    reset ();
  }

  // create a pki key by key

  Pkdhe::Pkdhe (Key* pkey) {
    // check for valid key
    if ((pkey != nullptr) && (pkey->gettype () != Key::CKEY_KDHE)) {
      throw Exception ("pkdhe-error" "invalid key type for pkdhe");
    }
    if (pkey != nullptr) {
      // check for configure
      d_pemc = pkey->isconfig () ? Pem::PEMC_DHEM : Pem::PEMC_NONE;
      // check for public
      if (pkey->ispublic () == true) d_pemc = Pem::PEMC_DHEK;
      // check for private
      if (pkey->valid () == true) d_pemc = Pem::PEMC_DHEP;
    }
    Object::iref (p_pkey = pkey);
  }

  // create a pki key by path

  Pkdhe::Pkdhe (const String& path) {
    // preset objects
    p_pkey = nullptr;
    // read by path
    Pki::read (path);
    if (p_pkey != nullptr) {
      // check for configure
      d_pemc = p_pkey->isconfig () ? Pem::PEMC_DHEM : Pem::PEMC_NONE;
      // check for public
      if (p_pkey->ispublic () == true) d_pemc = Pem::PEMC_DHEK;
      // check for private
      if (p_pkey->valid () == true) d_pemc = Pem::PEMC_DHEP;
    }
  }
  
  // copy construct this pki key

  Pkdhe::Pkdhe (const Pkdhe& that) {
    that.rdlock ();
    try {
      // assign base object
      Pki::operator = (that);
      // copy locally
      p_pkey = (that.p_pkey == nullptr) ? nullptr : new Key (*that.p_pkey);
      Object::iref (p_pkey);
      // here it is
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }
      
  // destroy this pki key

  Pkdhe::~Pkdhe (void) {
    Object::dref (p_pkey);
  }

  // assign a pki key to this one

  Pkdhe& Pkdhe::operator = (const Pkdhe& that) {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      // assign base object
      Pki::operator = (that);
      // copy locally
      Object::dref (p_pkey);
      p_pkey = (that.p_pkey == nullptr) ? nullptr : new Key (*that.p_pkey);
      Object::iref (p_pkey);
      // here it is
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }
  
  // return the object class name

  String Pkdhe::repr (void) const {
    return "Pkdhe";
  }

  // return a clone of this object

  Object* Pkdhe::clone (void) const {
    return new Pkdhe (*this);
  }
  
  // reset the pki key

  void Pkdhe::reset (void) {
    wrlock ();
    try {
      // reset base object
      Pki::reset ();
      // reset locally
      Object::dref (p_pkey); p_pkey = nullptr;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the pki key

  Key* Pkdhe::getkey (void) const {
    rdlock ();
    try {
      Key* result = p_pkey;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - protected section                                                     -
  // -------------------------------------------------------------------------

  // encode the pki buffer

  bool Pkdhe::encode (void) {
    wrlock ();
    try {
      // extract the key data
      Relatif glop = p_pkey->getrkey (Key::KDHE_PPRM);
      Relatif glog = p_pkey->getrkey (Key::KDHE_GGEN);
      Relatif gloo = p_pkey->getrkey (Key::KDHE_GORD);
      Relatif skey = p_pkey->getrkey (Key::KDHE_SKEY);
      Relatif pkey = p_pkey->getrkey (Key::KDHE_PKEY);
      // check for parameter export only
      bool pflg = skey.iszero () && pkey.iszero ();
      // create a sequence
      t_transient<AsnSequence> pseq = new AsnSequence;
      if (pflg == true) {
	pseq->add (new AsnInteger(glop));
	pseq->add (new AsnInteger(glog));
	pseq->add (new AsnInteger(gloo));
      } else {
	// add the version
	pseq->add (new AsnInteger(0));
	// add the key data
	pseq->add (new AsnInteger(glop));
	pseq->add (new AsnInteger(glog));
	pseq->add (new AsnInteger(gloo));
	pseq->add (new AsnInteger(pkey));
	pseq->add (new AsnInteger(skey));
      }
      // write the sequence
      pseq->write (d_xbuf);
      // clean and return
      unlock ();
      return true;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // decode the pki buffer
  
  bool Pkdhe::decode (void) {
    wrlock ();
    try {
      // create an asn buffer
      AsnBuffer abuf (d_xbuf);
      // map the buffer to a sequence
      t_transient<AsnSequence> pseq = pkdhe_topseq (abuf);
      long nlen = pseq->getnlen ();
      // prepare the key vector
      Vector kvec;
      // check for a parameter only
      if (nlen == 3) {
	// get the key elements
	Relatif glop = pkdhe_torval (*pseq, 0);
	Relatif glog = pkdhe_torval (*pseq, 1);
	Relatif gloo = pkdhe_torval (*pseq, 2);
	// create the key vector
	kvec.add (new Relatif (glop));
	kvec.add (new Relatif (glog));
	kvec.add (new Relatif (gloo));
      }
      // check the version
      if (nlen == 6) {
	long vers = pkdhe_torval(*pseq, 0).tolong ();
	if (vers != 0L) {
	  throw Exception ("pkdhe-error", "invalid dhe key version");
	}
      }
      // check for a full key
      if ((nlen == 5) || (nlen == 6)){
	// get the offset
	long noff = nlen - 5;
	// get the key elements
	Relatif glop = pkdhe_torval (*pseq, 0 + noff);
	Relatif glog = pkdhe_torval (*pseq, 1 + noff);
	Relatif gloo = pkdhe_torval (*pseq, 2 + noff);
	Relatif pkey = pkdhe_torval (*pseq, 3 + noff);
	Relatif skey = pkdhe_torval (*pseq, 4 + noff);
	// create the key vector
	kvec.add (new Relatif (glop));
	kvec.add (new Relatif (glog));
	kvec.add (new Relatif (gloo));
	kvec.add (new Relatif (skey));
	kvec.add (new Relatif (pkey));
      }
      // create the key
      Object::iref (p_pkey = new Key (Key::CKEY_KDHE, kvec));
      // unlock and return
      unlock ();
      return true;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 1;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the object supported quarks
  static const long QUARK_GETKEY = zone.intern ("get-key");

  // create a new object in a generic way
 
  Object* Pkdhe::mknew (Vector* argv) {
    long argc = (argv == nullptr) ? 0 : argv->length ();

    // create a default object
    if (argc == 0) return new Pkdhe;
    // check for 1 argument
    if (argc == 1) {
      Object* obj = argv->get (0);
      // check for a string
      String* path = dynamic_cast <String*> (obj);
      if (path != nullptr) return new Pkdhe (*path);
      // check for a key
      Key* key = dynamic_cast <Key*> (obj);
      if (key != nullptr) return new Pkdhe (key);
      // invalid object
      throw Exception ("type-error", "invalif object with pkdhe",
		       Object::repr (obj));
    }
    // too many arguments
    throw Exception ("argument-error",
                     "too many argument with pki constructor");
  }

  // return true if the given quark is defined

  bool Pkdhe::isquark (const long quark, const bool hflg) const {
    rdlock ();
    try {
      if (zone.exists (quark) == true) {
	unlock ();
	return true;
      }
      bool result = hflg ? Pki::isquark (quark, hflg) : false;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // apply this object with a set of arguments and a quark

  Object* Pkdhe::apply (Evaluable* zobj, Nameset* nset, const long quark,
			Vector* argv) {
    // get the number of arguments
    long argc = (argv == nullptr) ? 0 : argv->length ();
    
    // check for 0 argument
    if (argc == 0) {
      if (quark == QUARK_GETKEY) {
	rdlock ();
	try {
	  Object* result = getkey ();
	  zobj->post (result);
	  unlock ();
	  return result;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
    }
    // call the pki method
    return Pki::apply (zobj, nset, quark, argv);
  }
}
