// ---------------------------------------------------------------------------
// - InputCipher.cpp                                                         -
// - afnix cryptography - input cipher 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-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Item.hpp"
#include "Vector.hpp"
#include "Utility.hpp"
#include "Runnable.hpp"
#include "QuarkZone.hpp"
#include "InputCipher.hpp"

namespace afnix {

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

  // create a new input cipher stream by cipher only

  InputCipher::InputCipher (Cipher* cifr) {
    // bind object
    Object::iref (p_cifr = cifr);
    p_is = nilp;
    // grab cipher information
    d_mode = ECB;
    d_cbsz = p_cifr == nilp ? 0     : p_cifr->getcbsz ();
    d_rflg = p_cifr == nilp ? false : p_cifr->getrflg ();
    // create buffer blocks
    p_bo = new t_byte[d_cbsz];
    p_bi = new t_byte[d_cbsz];
    p_bl = new t_byte[d_cbsz];
    // and now initialize eveything
    initialize ();
  }

  // create a new input cipher stream by cipher and mode

  InputCipher::InputCipher (Cipher* cifr, const t_mode mode) {
    // bind object
    Object::iref (p_cifr = cifr);
    p_is = nilp;
    // grab cipher information
    d_mode = mode;
    d_cbsz = p_cifr == nilp ? 0     : p_cifr->getcbsz ();
    d_rflg = p_cifr == nilp ? false : p_cifr->getrflg ();
    // create buffer block
    p_bo = new t_byte[d_cbsz];
    p_bi = new t_byte[d_cbsz];
    p_bl = new t_byte[d_cbsz];
    // and now initialize eveything
    initialize ();
  }

  // create a new input cipher stream by cipher and input stream

  InputCipher::InputCipher (Cipher* cifr, Input* is) {
    // bind object
    Object::iref (p_cifr = cifr);
    Object::iref (p_is = is);
    // grab cipher information
    d_mode = ECB;
    d_cbsz = p_cifr == nilp ? 0     : p_cifr->getcbsz ();
    d_rflg = p_cifr == nilp ? false : p_cifr->getrflg ();
    // create buffer blocks
    p_bo = new t_byte[d_cbsz];
    p_bi = new t_byte[d_cbsz];
    p_bl = new t_byte[d_cbsz];
    // and now initialize eveything
    initialize ();
  }

  // create a new input cipher stream by cipher, input stream and mode

  InputCipher::InputCipher (Cipher* cifr, Input* is, const t_mode mode) {
    // bind object
    Object::iref (p_cifr = cifr);
    Object::iref (p_is = is);
    // grab cipher information
    d_mode = mode;
    d_cbsz = p_cifr == nilp ? 0     : p_cifr->getcbsz ();
    d_rflg = p_cifr == nilp ? false : p_cifr->getrflg ();
    // create buffer block
    p_bo = new t_byte[d_cbsz];
    p_bi = new t_byte[d_cbsz];
    p_bl = new t_byte[d_cbsz];
    // and now initialize eveything
    initialize ();
  }

  // destroy this input cipher

  InputCipher::~InputCipher (void) {
    Object::dref (p_cifr);
    Object::dref (p_is);
  }

  // return the class name

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

  // duplicate this input cipher with an input stream

  InputCipher* InputCipher::dup (Input* is) const {
    rdlock ();
    InputCipher* result = new InputCipher (p_cifr, is, d_mode);
    unlock ();
    return result;
  }

  // set the input cipher input stream
  
  void InputCipher::setis (Input* is) {
    wrlock ();
    try {
      // compute initialize flag
      bool init = p_is == nilp;
      // update input stream
      Object::iref (is);
      Object::dref (p_is);
      p_is = is;
      // eventually initialize
      if (init == true) initialize ();
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // private initialize method

  void InputCipher::initialize (void) {
    // reset buffer block 
    for (long i = 0; i < d_cbsz; i++) {
      p_bo[i] = nilc;
      p_bi[i] = nilc;
      p_bl[i] = nilc;
    }
    // check for cipher or stream
    if ((p_cifr == nilp) || (p_is == nilp)) return;
    // compute first block in cbc mode
    if (d_mode == CBC) {
      // in normal mode, initialize the IV block with random characters
      // process it and add it to the buffer
      if (d_rflg == false) {
	for (long i = 0; i < d_cbsz; i++) p_bi[i] = Utility::byternd ();
	// process the block
	p_cifr->process (p_bo, p_bi);
	// add the block in the buffer
	d_buffer.add ((const char*) p_bo, d_cbsz);
      }
      // in reverse mode, read the first block and decrypt it
      // and throw it away (almost - since it is in the bl buffer)
      if (d_rflg == true) {
	long count = 0;
	while (p_is->valid (-1) == true) {
	  p_bl[count++] = p_is->read ();
	  if (count == d_cbsz) break;
	}
	if (count != d_cbsz) {
	  throw Exception ("cipher-error", "initial short block in cbc mode");
	}
      }
    }
  }

  // update the input buffer

  void InputCipher::update (void) {
    wrlock ();
    // check the input buffer first
    if (d_buffer.empty () == false) {
      unlock ();
      return;
    }
    // check if we can read the input stream
    if ((p_is == nilp) || (p_is->iseof () == true)) {
      unlock ();
      return;
    }
    // check if we have a block cipher
    if (p_cifr == nilp) {
      unlock ();
      return;
    }
    // fill in a block buffer
    long count = 0;
    while (p_is->valid (-1) == true) {
      p_bi[count++] = p_is->read ();
      if (count == d_cbsz) break;
    }
    // if the block is not full and we are in reverse
    // then, we do have a problem
    if ((count != d_cbsz) && (d_rflg == true)) {
      unlock ();
      throw Exception ("cipher-error", "short block in reverse mode");
    }
    // reset pad flag
    bool pflg = false;
    // if the block is not full, compute the padding which
    // must be less than the character range
    if ((count != d_cbsz) && (d_rflg == false)) {
      long pad = d_cbsz - count;
      // check the padding
      if (pad > 256) {
	unlock ();
	throw Exception ("cipher-error", "block padding is too long");
      }
      // pad with the pad size as recommended by RFC 2630 and NIST 800-38A
      for (long i = count; i < d_cbsz; i++) p_bi[i] = pad;
      // reset the count at block size and mark padded
      count = d_cbsz;
      pflg  = true;
    }
    // here the block is full - in normal cbc mode, perform the block
    // xor before processing
    if ((d_mode == CBC) && (d_rflg == false)) {
      for (long i = 0; i < d_cbsz; i++) p_bi[i] ^= p_bo[i];
    }
    // now process the block through the cipher
    p_cifr->process (p_bo, p_bi);
    // in reverse cbc mode, perform the block xor with the preceeding
    // input block and save last block
    if ((d_mode == CBC) && (d_rflg == true)) {
      for (long i = 0; i < d_cbsz; i++) {
	p_bo[i] ^= p_bl[i];
	p_bl[i]  = p_bi[i];
      }
    }
    // in reverse mode we push only upto the pad if we are at the end 
    // of stream
    if ((d_rflg == true) && (p_is->iseof () == true)) {
      // compute padding and check
      long pad = p_bo[d_cbsz-1];
      if (pad > d_cbsz) {
	unlock ();
	throw Exception ("cipher-error", "block padding is too long");	
      }
      // update counter
      count = d_cbsz - pad;
    }
    // add the block in the buffer - if the last block was a full padding
    // block it is not pushed as the counter is 0
    d_buffer.add ((const char*) p_bo, count);
    // special case - in normal mode if the count is the same as the
    // block size and we have no more character, so we must push a complete
    // padding block - of course the pad flag is false
    if ((p_is->iseof () == true) && 
	(d_rflg == false) && (count == d_cbsz) && (pflg == false)) {
      for (long i = 0; i < d_cbsz; i++) p_bi[i] = d_cbsz;
      // xor in cbc mode
      if (d_mode == CBC) {
	for (long i = 0; i < d_cbsz; i++) p_bi[i] ^= p_bo[i];
      }
      // now process the block through the cipher
      p_cifr->process (p_bo, p_bi);
      // finally add it to the buffer
      d_buffer.add ((const char*) p_bo, d_cbsz);
      // mark padded
      pflg = true;
    }
    // unlock as we are done
    unlock ();
  }

  // read one byte from the input stream.

  char InputCipher::read (void) {
    wrlock ();
    // eventually update the buffer
    update ();
    // read a character
    char result = d_buffer.read ();
    // eventually update again the buffer
    update ();
    // unlock and return
    unlock ();
    return result;
  }

  // return true if we are at the eof

  bool InputCipher::iseof (void) const {
    rdlock ();
    bool result = (p_is == nilp) ? true : 
                   d_buffer.empty () && p_is->iseof ();
    unlock ();
    return result;
  }

  // return true if we can read a character

  bool InputCipher::valid (const long tout) const {
    rdlock ();
    bool result = (p_is == nilp) ? false : 
                  (d_buffer.empty () == false) || p_is->valid (tout);
    unlock ();
    return result;
  }

  // get the normal waist from a file size

  t_long InputCipher::waist (const t_long size) const {
    rdlock ();
    // normalize the size
    t_long result = (p_cifr == nilp) ? size : p_cifr->waist (size);
    // if the size is module the block size, we need to add a pad block
    if (p_cifr != nilp) {
      if ((size > 0) && ((size % d_cbsz) == 0)) result += d_cbsz;
      // in cbc mode, we need to add one block
      if (d_mode == CBC) result += d_cbsz;
    }
    unlock ();
    return result;
  }

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

  // the object eval quarks
  static const long QUARK_ECB     = String::intern ("ECB");
  static const long QUARK_CBC     = String::intern ("CBC");
  static const long QUARK_ICIPHER = String::intern ("InputCipher");

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

  // the object supported quarks
  static const long QUARK_DUP     = zone.intern ("dup");
  static const long QUARK_SETIS   = zone.intern ("set-is");

  // map an enumeration item to an input cipher mode
  static inline InputCipher::t_mode item_to_mode (const Item& item) {
    // check for an input cipher item
    if (item.gettid () != QUARK_ICIPHER)
      throw Exception ("item-error", "item is not an input cipher item");
    // map the item to the enumeration
    long quark = item.getquark ();
    if (quark == QUARK_ECB) return InputCipher::ECB;
    if (quark == QUARK_CBC) return InputCipher::CBC;
    throw Exception ("item-error", "cannot map item to input cipher mode");
  }

  // evaluate an object data member

  Object* InputCipher::meval (Runnable* robj, Nameset* nset, 
			      const long quark) {
    if (quark == QUARK_ECB) 
      return new Item (QUARK_ICIPHER, QUARK_ECB);
    if (quark == QUARK_CBC) 
      return new Item (QUARK_ICIPHER, QUARK_CBC);
    throw Exception ("eval-error", "cannot evaluate member",
		     String::qmap (quark));
  }

  // create a new object in a generic way

  Object* InputCipher::mknew (Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // check for 1 argument
    if (argc == 1) {
      // get the cipher object
      Object* cobj = argv->get (0);
      Cipher* cipher = dynamic_cast <Cipher*> (cobj);
      if (cipher == nilp) {
	throw Exception ("argument-error", 
			 "invalid arguments with input cipher");
      }
      return new InputCipher (cipher);
    }
    // check for 2 arguments
    if (argc == 2) {
      // get the cipher object
      Object* cobj = argv->get (0);
      Cipher* cipher = dynamic_cast <Cipher*> (cobj);
      if (cipher == nilp) {
	throw Exception ("argument-error", 
			 "invalid arguments with input cipher");
      }
      // get the input stream object
      Object* iobj = argv->get (1);
      Input*  is   = dynamic_cast <Input*> (iobj);
      if (is != nilp) {
	return new InputCipher (cipher, is);
      }
      // check for a mode
      Item* item = dynamic_cast <Item*> (iobj);
      if (item != nilp) {
	t_mode mode = item_to_mode (*item);
	return new InputCipher (cipher, mode);
      }
      throw Exception ("argument-error", 
		       "invalid arguments with input cipher");
    }
    // check for 3 arguments
    if (argc == 3) {
      // get the cipher object
      Object* cobj = argv->get (0);
      Cipher* cipher = dynamic_cast <Cipher*> (cobj);
      if (cipher == nilp) {
	throw Exception ("argument-error", 
			 "invalid arguments with input cipher");
      }
      // get the input stream object
      Object* iobj = argv->get (1);
      Input*  is   = dynamic_cast <Input*> (iobj);
      if (is == nilp) {
	throw Exception ("argument-error", 
			 "invalid arguments with input cipher");
      }
      // get the mode as an item
      Object* mobj = argv->get (2);
      Item*   item = dynamic_cast <Item*> (mobj);
      if (item == nilp) {
	throw Exception ("argument-error", 
			 "invalid arguments with input cipher");
      }
      t_mode mode = item_to_mode (*item);
      return new InputCipher (cipher, is, mode);
    }
    throw Exception ("argument-error", 
		     "invalid arguments with with input cipher");
  }

  // return true if the given quark is defined

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

  // apply this object with a set of arguments and a quark
  
  Object* InputCipher::apply (Runnable* robj, Nameset* nset, const long quark,
			      Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_DUP) {
	Object* obj = argv->get (0);
	Input*  is  = dynamic_cast <Input*> (obj);
	if (is == nilp) {
	  throw Exception ("type-error", 
			   "invalid input object for input cipher set-is",
			   Object::repr (obj));
	}
	rdlock ();
        try {
          Object* result = dup (is);
          robj->post (result);
          unlock ();
          return result;
        } catch (...) {
          unlock ();
          throw;
        }
      }
      if (quark == QUARK_SETIS) {
	Object* obj = argv->get (0);
	Input*  is  = dynamic_cast <Input*> (obj);
	if (is == nilp) {
	  throw Exception ("type-error", 
			   "invalid input object for input cipher set-is",
			   Object::repr (obj));
	}
	setis (is);
	return nilp;
      }
    }
    // call the input stream method
    return Input::apply (robj, nset, quark, argv);
  }
}
