// ---------------------------------------------------------------------------
// - Key.cpp                                                                 -
// - afnix:sec module - key class 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-2009 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Key.hpp"
#include "Byte.hpp"
#include "Item.hpp"
#include "Ascii.hpp"
#include "Prime.hpp"
#include "Vector.hpp"
#include "Crypto.hpp"
#include "Unicode.hpp"
#include "Utility.hpp"
#include "Integer.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

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

  // 128 bits key size
  static const long KEY_128_BITS = 128;
  static const long KEY_128_SIZE = 128 / 8;

  // the default symmetric key size in bits
  static const long KEY_SYM_BITS = KEY_128_BITS;
  static const long KEY_SYM_SIZE = KEY_SYM_BITS / 8;

  // the default rsa key size in bits
  static const long KEY_RSA_BITS = 1024;
  // the recommended rsa key exponent (NIST)
  static const long KEY_RSA_REXP = 65537;

  // the default mac key size in bits
  static const long KEY_MAC_BITS = KEY_128_BITS;
  static const long KEY_MAC_SIZE = KEY_MAC_BITS / 8;

  // this procedure extract a relatif from an object
  static inline Relatif torel (Object* obj) {
    // check for nil
    if (obj == nilp) {
      throw Exception ("type-error", "invalid nil object as relatif");
    }
    // check for an integer
    Integer* iobj = dynamic_cast <Integer*> (obj);
    if (iobj != nilp) return iobj->tointeger ();
    // check fo a relatif
    Relatif* robj = dynamic_cast <Relatif*> (obj);
    if (robj != nilp) return *robj;
    // invalid object
    throw Exception ("type-error", "invalid object as a relatif",
		     Object::repr (obj));    
  }

  // the symmetric cipher key
  struct s_ksym {
    // the number of bytes
    long    d_size;
    // the key buffer
    t_byte* p_kbuf;
    // create a default key
    s_ksym (void) {
      d_size = 0;
      p_kbuf = nilp;
    }
    // create a key by octet string
    s_ksym (const String& ostr) {
      p_kbuf = Unicode::stob (d_size, ostr);
    }
    // create a key by size and data
    s_ksym (const long size, const t_byte* kval) {
      d_size = size;
      p_kbuf = new t_byte[d_size];
      for (long i = 0; i < d_size; i++) p_kbuf[i] = kval[i];
    }
    // copy construct this key
    s_ksym (const s_ksym& that) {
      d_size = that.d_size;
      p_kbuf = new t_byte[d_size];
      for (long i = 0; i < d_size; i++) p_kbuf[i] = that.p_kbuf[i];
    }
    // destroy this key
    ~s_ksym (void) {
      delete [] p_kbuf;
    }   
    // return the key size in bits
    long getbits (void) const {
      return d_size * 8;
    }
    // return the key size in byts
    long getsize (void) const {
      return d_size;
    }
    // format the key as a string
    String format (void) const {
      return Ascii::btos (p_kbuf, d_size);
    }
    // load by size with random values
    void ldrand (const long size) {
      // delete old key
      d_size = size;
      delete [] p_kbuf;
      // create buffer and initialize
      p_kbuf = new t_byte[d_size];
      for (long i = 0; i < d_size; i++) p_kbuf[i] = Utility::byternd (); 
    }
  };

  // the rsa key structure
  struct s_krsa {
    // the modulus
    Relatif d_pmod;
    // the public exponent
    Relatif d_pexp;
    // the secret exponent
    Relatif d_sexp;
    // the secret prime p
    Relatif d_spvp;
    // the secret prime q
    Relatif d_spvq;
    // the crt p exponent
    Relatif d_crtp;
    // the crt q exponent
    Relatif d_crtq;
    // the crt q inverse
    Relatif d_crti;
    // create a null key
    s_krsa (void) {
      d_pmod = 0;
      d_pexp = 0;
      d_sexp = 0;
      d_spvp = 0;
      d_spvq = 0;
      d_crtp = 0;
      d_crtq = 0;
      d_crti = 0;
      
    }
    // copy construct this rsa key
    s_krsa (const s_krsa& that) {
      d_pmod = that.d_pmod;
      d_pexp = that.d_pexp;
      d_sexp = that.d_sexp;
      d_spvp = that.d_spvp;
      d_spvq = that.d_spvq;
      d_crtp = that.d_crtp;
      d_crtq = that.d_crtq;
      d_crti = that.d_crti;
    }
    // return the key size in bits
    long getbits (void) const {
      return d_pmod.getmsb ();
    }
    // return the key size in byts
    long getsize (void) const {
      return d_pmod.getmsb () / 8;
    }
    // return a formatted key by type
    String format (const Key::t_ikey type) const {
      if (type == Key::KRSA_PMOD) return d_pmod.tohexa ();
      if (type == Key::KRSA_PEXP) return d_pexp.tohexa ();
      if (type == Key::KRSA_SEXP) return d_sexp.tohexa ();
      throw Exception ("key-error", "invalid rsa key accessor");
    }
    // return a relatif key by type
    Relatif getrkey (const Key::t_ikey type) const {
      if (type == Key::KRSA_PMOD) return d_pmod;
      if (type == Key::KRSA_PEXP) return d_pexp;
      if (type == Key::KRSA_SEXP) return d_sexp;
      throw Exception ("key-error", "invalid rsa key accessor");
    }
    // create a random key by size
    void ldrand (const long bits, const Relatif& e) {
      // check the size
      if (bits < 32) {
	throw Exception ("key-error", "random rsa key size is to small");
      }
      // check for parity
      if ((bits % 2) != 0) {
	throw Exception ("key-error", "random rsa key size is not even");
      }
      do {
	// adjust to even
	long pbit = (bits + 1) / 2;
	long qbit = bits - pbit;
	// get two prime numbers
	d_spvp = Prime::random (pbit);
	d_spvq = Prime::random (qbit);
	// compute the modulus
	d_pmod = d_spvp * d_spvq;
      } while ((d_spvp == d_spvq) || (d_pmod.getmsb () != bits));
      // swap p and q if p > q
      if (d_spvp < d_spvq) {
	Relatif t = d_spvp;
	d_spvp = d_spvq;
	d_spvq = t;
      }
      // get the lcm (originally the totient)
      Relatif m = Relatif::lcm (d_spvp - 1, d_spvq - 1);
      // verify that e is coprime with m
      if (Relatif::gcd (m, e) != 1) {
	throw Exception ("key-error", "random rsa key exponent not valid");
      }
      // update key values
      d_pexp = e;
      d_sexp = Relatif::mmi (e, m);
      // compute crt values
      d_crtp = d_sexp % (d_spvp - 1);
      d_crtq = d_sexp % (d_spvq - 1);
      d_crti = Relatif::mmi (d_spvq, d_spvp);
    }
    // load a key by a number vector
    void ldnvec (const Vector& nvec) {
      // check vector length
      long vlen = nvec.length ();
      if ((vlen != 3) && (vlen != 8)) {
	throw Exception ("key-error", "invalid rsa key vector size");
      }
      // set base elements
      d_pmod = torel (nvec.get (0));
      d_pexp = torel (nvec.get (1));
      d_sexp = torel (nvec.get (2));
      // set extra elements
      if (vlen == 8) {
	d_spvp = torel (nvec.get (3));
	d_spvq = torel (nvec.get (4));
	d_crtp = torel (nvec.get (5));
	d_crtq = torel (nvec.get (6));
	d_crti = torel (nvec.get (7));
      }
    }
  };

  // the mac key
  struct s_kmac {
    // the number of bytes
    long    d_size;
    // the key buffer
    t_byte* p_kbuf;
    // create a default key
    s_kmac (void) {
      d_size = 0;
      p_kbuf = nilp;
    }
    // create a key by octet string
    s_kmac (const String& ostr) {
      p_kbuf = Unicode::stob (d_size, ostr);
    }
    // create a key by size and data
    s_kmac (const long size, const t_byte* kval) {
      d_size = size;
      p_kbuf = new t_byte[d_size];
      for (long i = 0; i < d_size; i++) p_kbuf[i] = kval[i];
    }
    // copy construct this key
    s_kmac (const s_kmac& that) {
      d_size = that.d_size;
      p_kbuf = new t_byte[d_size];
      for (long i = 0; i < d_size; i++) p_kbuf[i] = that.p_kbuf[i];
    }
    // destroy this key
    ~s_kmac (void) {
      delete [] p_kbuf;
    }   
    // return the key size in bits
    long getbits (void) const {
      return d_size * 8;
    }
    // return the key size in byts
    long getsize (void) const {
      return d_size;
    }
    // format the key as a string
    String format (void) const {
      return Ascii::btos (p_kbuf, d_size);
    }
    // load by size with random values
    void ldrand (const long size) {
      // delete old key
      d_size = size;
      delete [] p_kbuf;
      // create buffer and initialize
      p_kbuf = new t_byte[d_size];
      for (long i = 0; i < d_size; i++) p_kbuf[i] = Utility::byternd (); 
    }
  };

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

  // create a default random key

  Key::Key (void) {
    // default symmetric 128 bits key
    d_type = KSYM;
    p_ksym = new s_ksym;
    // load the key with random values
    p_ksym->ldrand (KEY_SYM_SIZE);
  }

  // create a default key by type

  Key::Key (const t_ckey type) {
    // save the type
    d_type = type;
    // check for symmetric key
    if (d_type == KSYM) {
      p_ksym = new s_ksym;
      // load the key with random values
      p_ksym->ldrand (KEY_SYM_SIZE);
    }
    // check for rsa key
    if (d_type == KRSA) {
      p_krsa = new s_krsa;
      // load the key with random values
      p_krsa->ldrand (KEY_RSA_BITS, KEY_RSA_REXP);
    }
    // check for mac key
    if (d_type == KMAC) {
      p_kmac = new s_kmac;
      // load the key with random values
      p_kmac->ldrand (KEY_MAC_SIZE);
    }
  }

  // create a symmetric key by octet string

  Key::Key (const String& ostr) {
    d_type = KSYM;
    p_ksym = new s_ksym (ostr);
  }

  // create a key by type and bit size

  Key::Key (const t_ckey type, const long bits) {
    // check the bit size
    if (bits <= 0) {
      throw Exception ("size-error", "invalid key bit size");
    }
    // check for symmetric key
    if (type == KSYM) {
      if ((bits % 8) != 0) {
	throw Exception ("size-error", 
			 "unaligned byte size for symmetric key");
      }
      // save the type
      d_type = type;
      // create the symmetric key
      p_ksym = new s_ksym;
      // load the key with random byte
      p_ksym->ldrand (bits/8);
    } 
    // check for rsa key
    if (type == KRSA) {
      // save the type
      d_type = type;
      // create the rsa key
      p_krsa = new s_krsa;
      // load the key with random primes
      p_krsa->ldrand (bits, KEY_RSA_REXP);
    }
    // check for mac key
    if (type == KMAC) {
      if ((bits % 8) != 0) {
	throw Exception ("size-error", 
			 "unaligned byte size for mac key");
      }
      // save the type
      d_type = type;
      // create the mac key
      p_kmac = new s_kmac;
      // load the key with random byte
      p_kmac->ldrand (bits/8);
    } 
  }

  // create a key by type and octet string

  Key::Key (const t_ckey type, const String& ostr) {
    // save the type
    d_type = type;
    // check for symmetric key
    if (d_type == KSYM) {
      p_ksym = new s_ksym (ostr);
    }
    // check for rsa key
    if (type == KRSA) {
      throw Exception ("type-error", "invalid key type by octet string");
    }
    // check for mac key
    if (d_type == KMAC) {
      p_kmac = new s_kmac (ostr);
    }
  }

  // create a key by type and object vector
  
  Key::Key (const t_ckey type, const Vector& ovec) {
    // check for symmetric key
    if (type == KSYM) {
      throw Exception ("type-error", "invalid key type by object vector");
    }
    // check key type
    if (type == KRSA) {
      // save the type
      d_type = type;
      // create the rsa key
      p_krsa = new s_krsa;
      // load the key with a number vector
      p_krsa->ldnvec (ovec);
    }
    // check for mac key
    if (type == KMAC) {
      throw Exception ("type-error", "invalid key type by object vector");
    }
  }

  // copy construct this key

  Key::Key (const Key& that) {
    that.rdlock ();
    d_type = that.d_type;
    switch (d_type) {
    case KSYM:
      p_ksym = new s_ksym (*that.p_ksym);
      break;
    case KRSA:
      p_krsa = new s_krsa (*that.p_krsa);
      break;
    case KMAC:
      p_kmac = new s_kmac (*that.p_kmac);
      break;
    }
    that.unlock ();
  }

  // destroy this key

  Key::~Key (void) {
    switch (d_type) {
    case KSYM:
      delete p_ksym;
      break;
    case KRSA:
      delete p_krsa;
      break;
    case KMAC:
      delete p_kmac;
      break;
    default:
      break;
    }
  }

  // assign a key to this one

  Key& Key::operator = (const Key& that) {
    wrlock ();
    that.rdlock ();
    if (this != &that) {
      // clean the old key
      switch (d_type) {
      case KSYM:
	delete p_ksym;
	p_ksym = nilp;
	break;
      case KRSA:
	delete p_krsa;
	p_krsa = nilp;
	break;
      case KMAC:
	delete p_kmac;
	p_kmac = nilp;
	break;
      }
      d_type = that.d_type;
      // set the new key
      switch (d_type) {
      case KSYM:
	p_ksym = new s_ksym (*that.p_ksym);
	break;
      case KRSA:
	delete p_krsa;
	p_krsa = new s_krsa (*that.p_krsa);
	break;
      case KMAC:
	delete p_kmac;
	p_kmac = new s_kmac (*that.p_kmac);
	break;
      }
    }
    that.unlock ();
    unlock ();
    return *this;
  }

  // return the class name

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

  // return true if the key is a block cipher key

  bool Key::isbk (void) const {
    rdlock ();
    try {
      bool result = false;
      switch (d_type) {
      case KSYM:
	result = true;
	break;
      default:
	break;
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return true if the key is a public cipher key

  bool Key::ispk (void) const {
    rdlock ();
    try {
      bool result = false;
      switch (d_type) {
      case KRSA:
	result = true;
	break;
      default:
	break;
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return true if the key is a mac key

  bool Key::ismk (void) const {
    rdlock ();
    try {
      bool result = false;
      switch (d_type) {
      case KMAC:
	result = true;
	break;
      default:
	break;
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the key type

  Key::t_ckey Key::gettype (void) const {
    rdlock ();
    try {
      t_ckey result = d_type;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the key size in bits

  long Key::getbits (void) const {
    rdlock ();
    try {
      long result = 0;
      switch (d_type) {
      case KSYM:
	result = p_ksym->getbits ();
	break;
      case KRSA:
	result = p_krsa->getbits ();
	break;
      case KMAC:
	result = p_kmac->getbits ();
	break;
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the key size in bytes

  long Key::getsize (void) const {
    rdlock ();
    try {
      long result = 0;
      switch (d_type) {
      case KSYM:
	result = p_ksym->getsize ();
	break;
      case KRSA:
	result = p_krsa->getsize ();
	break;
      case KMAC:
	result = p_kmac->getsize ();
	break;
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return a formatted key representation
  
  String Key::format (void) const {
    rdlock ();
    try {
      // check for symmetric key
      if (d_type == KSYM) {
	String result = p_ksym->format ();
	unlock ();
	return result;
      }
      // check for mac key
      if (d_type == KMAC) {
	String result = p_kmac->format ();
	unlock ();
	return result;
      }
      // invalid key type in format
      throw Exception ("type-error", "invalid key type in format");
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return a formatted key representation by type

  String Key::format (const Key::t_ikey type) const {
    rdlock ();
    try {
      // initialize result
      String result;
      // select the key
      switch (d_type) {
      case KRSA:
	result = p_krsa->format (type);
	break;
      default:
	throw Exception ("key-error", "unsupported key type accessor");
	break;
      }
      unlock ();
      return result;      
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return a key byte by index

  t_byte Key::getbyte (const long index) const {
    rdlock ();
    try {
      // check for symmetric key
      if (d_type == KSYM) {
	// get key size and check
	if ((index < 0) || (index >= p_ksym->d_size)) {
	  throw Exception ("key-error","index is out of bound");
	}
	// key key byte
	t_byte result = p_ksym->p_kbuf[index];
	unlock ();
	return result;
      }
      // check for mac key
      if (d_type == KMAC) {
	// get key size and check
	if ((index < 0) || (index >= p_kmac->d_size)) {
	  throw Exception ("key-error","index is out of bound");
	}
	// key key byte
	t_byte result = p_kmac->p_kbuf[index];
	unlock ();
	return result;
      }
      // invalid key type in format
      throw Exception ("key-error", "invalid key type in get byte");
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return a relatif key by type

  Relatif Key::getrkey (const t_ikey type) const {
    rdlock ();
    try {
      // initialize result
      Relatif result = 0;
      // select the key
      switch (d_type) {
      case KRSA:
	result = p_krsa->getrkey (type);
	break;
      default:
	throw Exception ("key-error", "unsupported key type accessor");
	break;
      }
      unlock ();
      return result;      
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

  // the object eval quarks
  static const long QUARK_KEY      = String::intern ("Key");
  static const long QUARK_KSYM     = String::intern ("KSYM");
  static const long QUARK_KRSA     = String::intern ("KRSA");
  static const long QUARK_KMAC     = String::intern ("KMAC");
  static const long QUARK_KRSAPMOD = String::intern ("RSA-MODULUS");
  static const long QUARK_KRSAPEXP = String::intern ("RSA-PUBLIC-EXPONENT");
  static const long QUARK_KRSASEXP = String::intern ("RSA-SECRET-EXPONENT");

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

  // the object supported quarks
  static const long QUARK_FORMAT  = String::intern ("format");
  static const long QUARK_GETBITS = String::intern ("get-bits");
  static const long QUARK_GETSIZE = String::intern ("get-size");
  static const long QUARK_GETTYPE = String::intern ("get-type");
  static const long QUARK_GETBYTE = String::intern ("get-byte");
  static const long QUARK_GETRKEY = String::intern ("get-relatif-key");
  
  // map an item to a key type
  static inline Key::t_ckey item_to_ckey (const Item& item) {
    // check for a key item
    if (item.gettid () != QUARK_KEY)
      throw Exception ("item-error", "item is not a key item");
    // map the item to the enumeration
    long quark = item.getquark ();
    if (quark == QUARK_KSYM) return Key::KSYM;
    if (quark == QUARK_KRSA) return Key::KRSA;
    if (quark == QUARK_KMAC) return Key::KMAC;
    throw Exception ("item-error", "cannot map item to key type");
  }

  // map a key type to an item
  static inline Item* ckey_to_item (const Key::t_ckey type) {
    switch (type) {
    case Key::KSYM:
      return new Item (QUARK_KEY, QUARK_KSYM);
      break;
    case Key::KRSA:
      return new Item (QUARK_KEY, QUARK_KRSA);
      break;
    case Key::KMAC:
      return new Item (QUARK_KEY, QUARK_KMAC);
      break;
    }
    return nilp;
  }

  // map an item to a key accessor
  static inline Key::t_ikey item_to_ikey (const Item& item) {
    // check for a key item
    if (item.gettid () != QUARK_KEY)
      throw Exception ("item-error", "item is not a key item");
    // map the item to the enumeration
    long quark = item.getquark ();
    if (quark == QUARK_KRSAPMOD) return Key::KRSA_PMOD;
    if (quark == QUARK_KRSAPEXP) return Key::KRSA_PEXP;
    if (quark == QUARK_KRSASEXP) return Key::KRSA_SEXP;
    throw Exception ("item-error", "cannot map item to key index accesoor");
  }

  // map a key accessor to an item
  static inline Item* ikey_to_item (const Key::t_ikey ikey) {
    switch (ikey) {
    case Key::KRSA_PMOD:
      return new Item (QUARK_KEY, QUARK_KRSAPMOD);
      break;
    case Key::KRSA_PEXP:
      return new Item (QUARK_KEY, QUARK_KRSAPEXP);
      break;
    case Key::KRSA_SEXP:
      return new Item (QUARK_KEY, QUARK_KRSASEXP);
      break;
    }
    return nilp;
  }

  // evaluate an object data member

  Object* Key::meval (Runnable* robj, Nameset* nset, const long quark) {
    // key type part
    if (quark == QUARK_KSYM)
      return new Item (QUARK_KEY, QUARK_KSYM);
    if (quark == QUARK_KRSA)
      return new Item (QUARK_KEY, QUARK_KRSA);
    if (quark == QUARK_KMAC)
      return new Item (QUARK_KEY, QUARK_KMAC);
    // key accessor
    if (quark == QUARK_KRSAPMOD)
      return new Item (QUARK_KEY, QUARK_KRSAPMOD);
    if (quark == QUARK_KRSAPEXP)
      return new Item (QUARK_KEY, QUARK_KRSAPEXP);
    if (quark == QUARK_KRSASEXP)
      return new Item (QUARK_KEY, QUARK_KRSASEXP);
    throw Exception ("eval-error", "cannot evaluate member",
                     String::qmap (quark));
  }
  
  // create a new object in a generic way
  
  Object* Key::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new Key;
    // check for 1 argument
    if (argc == 1) {
      Object* obj = argv->get (0);
      // check for a string
      String* sobj = dynamic_cast <String*> (obj);
      if (sobj != nilp) return new Key (*sobj);
      // check for an item type
      Item* iobj = dynamic_cast <Item*> (obj);
      if (iobj != nilp) {
        t_ckey type = item_to_ckey (*iobj);
        return new Key (type);
      }
      throw Exception ("type-error", "invalid object with key constructor",
                       Object::repr (obj));
    }
    // check for 2 arguments
    if (argc == 2) {
      // load first object
      Object* obj = argv->get (0);
      // check for an item type
      Item*  iobj = dynamic_cast <Item*> (obj);
      if (iobj == nilp) {
	throw Exception ("type-error", "invalid object with key constructor",
			 Object::repr (obj));
      }
      t_ckey type = item_to_ckey (*iobj);
      // load second object
      obj = argv->get (1);
      // check fo an integer
      Integer* nobj = dynamic_cast <Integer*> (obj);
      if (nobj != nilp) {     
	// get the number of bits
	long bits = nobj->tointeger ();
	// create the key
	return new Key (type, bits);
      }
      // check for a string
      String* sobj = dynamic_cast <String*> (obj);
      if (sobj != nilp) return new Key (type, *sobj);
      // check for a vector
      Vector* vobj = dynamic_cast <Vector*> (obj);
      if (vobj != nilp) return new Key (type, *vobj);
      // invalid type
      throw Exception ("type-error", "invalid object with key constructor",
		       Object::repr (obj));
    }
    // invalid arguments
    throw Exception ("argument-error", "too many arguments with key");
  }

  // return true if the given quark is defined

  bool Key::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true) {
      unlock ();
      return true;
    }
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // apply this object with a set of arguments and a quark
  
  Object* Key::apply (Runnable* robj, Nameset* nset, const long quark,
		      Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    
    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_FORMAT)  return new String   (format  ());
      if (quark == QUARK_GETBITS) return new Integer  (getbits ());
      if (quark == QUARK_GETSIZE) return new Integer  (getsize ());
      if (quark == QUARK_GETTYPE) return ckey_to_item (d_type);
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_GETBYTE) {
	long index = argv->getint (0);
	return new Byte (getbyte (index));
      }
      if (quark == QUARK_FORMAT) {
	// check for an item type
	Object* obj = argv->get (0);
	Item*  iobj = dynamic_cast <Item*> (obj);
	if (iobj == nilp) {
	  throw Exception ("type-error", "invalid object with format",
			   Object::repr (obj));
	}
	t_ikey ikey = item_to_ikey (*iobj);
	// get the relatif
	return new String (format (ikey));
      }
      if (quark == QUARK_GETRKEY) {
	// check for an item type
	Object* obj = argv->get (0);
	Item*  iobj = dynamic_cast <Item*> (obj);
	if (iobj == nilp) {
	  throw Exception ("type-error", "invalid object with get-relatif-key",
			   Object::repr (obj));
	}
	t_ikey ikey = item_to_ikey (*iobj);
	// get the relatif
	return new Relatif (getrkey (ikey));
      }      
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
