/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file hashVal.cxx
 * @author drose
 * @date 2000-11-14
 */

#include "hashVal.h"
#include "virtualFileSystem.h"
#include <ctype.h>

#ifdef HAVE_OPENSSL
#include "openSSLWrapper.h"  // must be included before any other openssl.
#include <openssl/md5.h>
#endif  // HAVE_OPENSSL

using std::istream;
using std::istringstream;
using std::ostream;
using std::ostringstream;
using std::string;


/**
 * Outputs the HashVal as a 32-digit hexadecimal number.
 */
void HashVal::
output_hex(ostream &out) const {
  char buffer[32];
  encode_hex(_hv[0], buffer);
  encode_hex(_hv[1], buffer + 8);
  encode_hex(_hv[2], buffer + 16);
  encode_hex(_hv[3], buffer + 24);
  out.write(buffer, 32);
}

/**
 * Inputs the HashVal as a 32-digit hexadecimal number.
 */
void HashVal::
input_hex(istream &in) {
  in >> std::ws;
  char buffer[32];
  size_t i = 0;
  int ch = in.get();

  while (ch != EOF && !in.fail() && isxdigit(ch)) {
    if (i < 32) {
      buffer[i] = (char)ch;
    }
    i++;
    ch = in.get();
  }

  if (i != 32) {
    in.clear(std::ios::failbit|in.rdstate());
    return;
  }

  if (ch != EOF) {
    in.putback((char)ch);
  } else {
    in.clear();
  }

  decode_hex(buffer, _hv[0]);
  decode_hex(buffer + 8, _hv[1]);
  decode_hex(buffer + 16, _hv[2]);
  decode_hex(buffer + 24, _hv[3]);
}

/**
 * Outputs the HashVal as a binary stream of bytes in order.  This is not the
 * same order generated by write_stream().
 */
void HashVal::
output_binary(ostream &out) const {
  StreamWriter writer(out);
  writer.add_be_uint32(_hv[0]);
  writer.add_be_uint32(_hv[1]);
  writer.add_be_uint32(_hv[2]);
  writer.add_be_uint32(_hv[3]);
}

/**
 * Inputs the HashVal as a binary stream of bytes in order.  This is not the
 * same order expected by read_stream().
 */
void HashVal::
input_binary(istream &in) {
  StreamReader reader(in);
  _hv[0] = reader.get_be_uint32();
  _hv[1] = reader.get_be_uint32();
  _hv[2] = reader.get_be_uint32();
  _hv[3] = reader.get_be_uint32();
}

/**
 * Returns the HashVal as a string with four decimal numbers.
 */
string HashVal::
as_dec() const {
  ostringstream strm;
  output_dec(strm);
  return strm.str();
}

/**
 * Sets the HashVal from a string with four decimal numbers.  Returns true if
 * valid, false otherwise.
 */
bool HashVal::
set_from_dec(const string &text) {
  istringstream strm(text);
  input_dec(strm);
  return !strm.fail();
}

/**
 * Returns the HashVal as a 32-byte hexadecimal string.
 */
string HashVal::
as_hex() const {
  char buffer[32];
  encode_hex(_hv[0], buffer);
  encode_hex(_hv[1], buffer + 8);
  encode_hex(_hv[2], buffer + 16);
  encode_hex(_hv[3], buffer + 24);
  return string(buffer, 32);
}

/**
 * Sets the HashVal from a 32-byte hexademical string.  Returns true if
 * successful, false otherwise.
 */
bool HashVal::
set_from_hex(const string &text) {
  istringstream strm(text);
  input_hex(strm);
  return !strm.fail();
}

/**
 * Returns the HashVal as a 16-byte binary string.
 */
vector_uchar HashVal::
as_bin() const {
  Datagram dg;
  write_datagram(dg);
  return vector_uchar((unsigned char *)dg.get_data(), (unsigned char *)dg.get_data() + dg.get_length());
}

/**
 * Sets the HashVal from a 16-byte binary string.  Returns true if successful,
 * false otherwise.
 */
bool HashVal::
set_from_bin(const vector_uchar &text) {
  nassertr(text.size() == 16, false);
  Datagram dg(text);
  DatagramIterator dgi(dg);
  read_datagram(dgi);
  return true;
}

#ifdef HAVE_OPENSSL
/**
 * Generates the hash value from the indicated file.  Returns true on success,
 * false if the file cannot be read.  This method is only defined if we have
 * the OpenSSL library (which provides md5 functionality) available.
 */
bool HashVal::
hash_file(const Filename &filename) {
  Filename bin_filename = Filename::binary_filename(filename);
  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  istream *istr = vfs->open_read_file(bin_filename, false);
  if (istr == nullptr) {
    (*this) = HashVal();
    return false;
  }

  bool result = hash_stream(*istr);
  vfs->close_read_file(istr);

  return result;
}
#endif  // HAVE_OPENSSL

#ifdef HAVE_OPENSSL
/**
 * Generates the hash value from the indicated file.  Returns true on success,
 * false if the file cannot be read.  This method is only defined if we have
 * the OpenSSL library (which provides md5 functionality) available.
 */
bool HashVal::
hash_stream(istream &stream) {
  unsigned char md[16];

  MD5_CTX ctx;
  MD5_Init(&ctx);

  static const int buffer_size = 1024;
  char buffer[buffer_size];

  // Seek the stream to the beginning in case it wasn't there already.
  stream.seekg(0, std::ios::beg);

  stream.read(buffer, buffer_size);
  size_t count = stream.gcount();
  while (count != 0) {
    MD5_Update(&ctx, buffer, count);
    stream.read(buffer, buffer_size);
    count = stream.gcount();
  }

  // Clear the fail bit so the caller can still read the stream (if it wants
  // to).
  stream.clear();

  MD5_Final(md, &ctx);

  // Store the individual bytes as big-endian ints, from historical
  // convention.
  _hv[0] = (md[0] << 24) | (md[1] << 16) | (md[2] << 8) | (md[3]);
  _hv[1] = (md[4] << 24) | (md[5] << 16) | (md[6] << 8) | (md[7]);
  _hv[2] = (md[8] << 24) | (md[9] << 16) | (md[10] << 8) | (md[11]);
  _hv[3] = (md[12] << 24) | (md[13] << 16) | (md[14] << 8) | (md[15]);

  return true;
}
#endif  // HAVE_OPENSSL


#ifdef HAVE_OPENSSL
/**
 * Generates the hash value by hashing the indicated data.  This method is
 * only defined if we have the OpenSSL library (which provides md5
 * functionality) available.
 */
void HashVal::
hash_buffer(const char *buffer, int length) {
  unsigned char md[16];
  MD5((const unsigned char *)buffer, length, md);

  // Store the individual bytes as big-endian ints, from historical
  // convention.
  _hv[0] = (md[0] << 24) | (md[1] << 16) | (md[2] << 8) | (md[3]);
  _hv[1] = (md[4] << 24) | (md[5] << 16) | (md[6] << 8) | (md[7]);
  _hv[2] = (md[8] << 24) | (md[9] << 16) | (md[10] << 8) | (md[11]);
  _hv[3] = (md[12] << 24) | (md[13] << 16) | (md[14] << 8) | (md[15]);
}

#endif  // HAVE_OPENSSL


/**
 * Encodes the indicated unsigned int into an eight-digit hex string, stored
 * at the indicated buffer and the following 8 positions.
 */
void HashVal::
encode_hex(uint32_t val, char *buffer) {
  buffer[0] = tohex(val >> 28);
  buffer[1] = tohex(val >> 24);
  buffer[2] = tohex(val >> 20);
  buffer[3] = tohex(val >> 16);
  buffer[4] = tohex(val >> 12);
  buffer[5] = tohex(val >> 8);
  buffer[6] = tohex(val >> 4);
  buffer[7] = tohex(val);
}

/**
 * Decodes the indicated eight-digit hex string into an unsigned integer.
 */
void HashVal::
decode_hex(const char *buffer, uint32_t &val) {
  unsigned int bytes[8];
  for (int i = 0; i < 8; i++) {
    bytes[i] = fromhex(buffer[i]);
  }

  val = ((bytes[0] << 28) |
         (bytes[1] << 24) |
         (bytes[2] << 20) |
         (bytes[3] << 16) |
         (bytes[4] << 12) |
         (bytes[5] << 8) |
         (bytes[6] << 4) |
         (bytes[7]));
}
