// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023 Eduardo Aguiar
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option) any later
// version.
//
// 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "hash_md5.h"
#include <cstring>
#include <config.h>

namespace mobius
{
namespace crypt
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// initialization constants (RFC 1321 - 3.3)
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static constexpr std::uint32_t A_VALUE = 0x67452301;
static constexpr std::uint32_t B_VALUE = 0xefcdab89;
static constexpr std::uint32_t C_VALUE = 0x98badcfe;
static constexpr std::uint32_t D_VALUE = 0x10325476;
static constexpr int BLOCK_SIZE = 64;           // 512 bits

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief rotate left a 32-bit value
//! \param v value
//! \param n number of bits to rotate
//! \return value rotated left by n bits
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
inline constexpr std::uint32_t
left_rotate (std::uint32_t v, std::uint32_t n) noexcept
{
  return (v << n) | (v >> (32 - n));
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief default constructor
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
hash_md5::hash_md5 () noexcept
  : hash_block (BLOCK_SIZE),
    a_ (A_VALUE),
    b_ (B_VALUE),
    c_ (C_VALUE),
    d_ (D_VALUE),
    size_ (0)
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief reset hash value
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
hash_md5::_reset () noexcept
{
  a_ = A_VALUE;
  b_ = B_VALUE;
  c_ = C_VALUE;
  d_ = D_VALUE;
  size_ = 0;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief update hash value
//! \param data data block
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
hash_md5::_update_block (const mobius::bytearray& data) noexcept
{
  std::uint32_t a = a_;
  std::uint32_t b = b_;
  std::uint32_t c = c_;
  std::uint32_t d = d_;
  std::uint32_t f;
  std::uint32_t temp;

  // M[0..15]
#ifdef WORDS_BIGENDIAN
  const std::uint32_t M [] =
  {
    (std::uint32_t (data[0])) |
    (std::uint32_t (data[1]) << 8) |
    (std::uint32_t (data[2]) << 16) |
    (std::uint32_t (data[3]) << 24),
    (std::uint32_t (data[4])) |
    (std::uint32_t (data[5]) << 8) |
    (std::uint32_t (data[6]) << 16) |
    (std::uint32_t (data[7]) << 24),
    (std::uint32_t (data[8])) |
    (std::uint32_t (data[9]) << 8) |
    (std::uint32_t (data[10]) << 16) |
    (std::uint32_t (data[11]) << 24),
    (std::uint32_t (data[12])) |
    (std::uint32_t (data[13]) << 8) |
    (std::uint32_t (data[14]) << 16) |
    (std::uint32_t (data[15]) << 24),
    (std::uint32_t (data[16])) |
    (std::uint32_t (data[17]) << 8) |
    (std::uint32_t (data[18]) << 16) |
    (std::uint32_t (data[19]) << 24),
    (std::uint32_t (data[20])) |
    (std::uint32_t (data[21]) << 8) |
    (std::uint32_t (data[22]) << 16) |
    (std::uint32_t (data[23]) << 24),
    (std::uint32_t (data[24])) |
    (std::uint32_t (data[25]) << 8) |
    (std::uint32_t (data[26]) << 16) |
    (std::uint32_t (data[27]) << 24),
    (std::uint32_t (data[28])) |
    (std::uint32_t (data[29]) << 8) |
    (std::uint32_t (data[30]) << 16) |
    (std::uint32_t (data[31]) << 24),
    (std::uint32_t (data[32])) |
    (std::uint32_t (data[33]) << 8) |
    (std::uint32_t (data[34]) << 16) |
    (std::uint32_t (data[35]) << 24),
    (std::uint32_t (data[36])) |
    (std::uint32_t (data[37]) << 8) |
    (std::uint32_t (data[38]) << 16) |
    (std::uint32_t (data[39]) << 24),
    (std::uint32_t (data[40])) |
    (std::uint32_t (data[41]) << 8) |
    (std::uint32_t (data[42]) << 16) |
    (std::uint32_t (data[43]) << 24),
    (std::uint32_t (data[44])) |
    (std::uint32_t (data[45]) << 8) |
    (std::uint32_t (data[46]) << 16) |
    (std::uint32_t (data[47]) << 24),
    (std::uint32_t (data[48])) |
    (std::uint32_t (data[49]) << 8) |
    (std::uint32_t (data[50]) << 16) |
    (std::uint32_t (data[51]) << 24),
    (std::uint32_t (data[52])) |
    (std::uint32_t (data[53]) << 8) |
    (std::uint32_t (data[54]) << 16) |
    (std::uint32_t (data[55]) << 24),
    (std::uint32_t (data[56])) |
    (std::uint32_t (data[57]) << 8) |
    (std::uint32_t (data[58]) << 16) |
    (std::uint32_t (data[59]) << 24),
    (std::uint32_t (data[60])) |
    (std::uint32_t (data[61]) << 8) |
    (std::uint32_t (data[62]) << 16) |
    (std::uint32_t (data[63]) << 24)
  };
#else
  std::uint32_t M[16];
  std::copy (data.begin (), data.end (), reinterpret_cast <uint8_t *> (M));
#endif

  // round 1
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xd76aa478 + M[0], 7);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xe8c7b756 + M[1], 12);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x242070db + M[2], 17);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xc1bdceee + M[3], 22);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xf57c0faf + M[4], 7);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x4787c62a + M[5], 12);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xa8304613 + M[6], 17);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xfd469501 + M[7], 22);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x698098d8 + M[8], 7);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x8b44f7af + M[9], 12);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xffff5bb1 + M[10], 17);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x895cd7be + M[11], 22);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x6b901122 + M[12], 7);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xfd987193 + M[13], 12);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xa679438e + M[14], 17);
  a = temp;
  f = (b & c) | (~b & d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x49b40821 + M[15], 22);
  a = temp;

  // round 2
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xf61e2562 + M[1], 5);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xc040b340 + M[6], 9);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x265e5a51 + M[11], 14);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xe9b6c7aa + M[0], 20);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xd62f105d + M[5], 5);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x02441453 + M[10], 9);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xd8a1e681 + M[15], 14);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xe7d3fbc8 + M[4], 20);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x21e1cde6 + M[9], 5);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xc33707d6 + M[14], 9);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xf4d50d87 + M[3], 14);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x455a14ed + M[8], 20);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xa9e3e905 + M[13], 5);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xfcefa3f8 + M[2], 9);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x676f02d9 + M[7], 14);
  a = temp;
  f = (d & b) | (~d & c);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x8d2a4c8a + M[12], 20);
  a = temp;

  // round 3
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xfffa3942 + M[5], 4);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x8771f681 + M[8], 11);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x6d9d6122 + M[11], 16);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xfde5380c + M[14], 23);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xa4beea44 + M[1], 4);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x4bdecfa9 + M[4], 11);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xf6bb4b60 + M[7], 16);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xbebfbc70 + M[10], 23);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x289b7ec6 + M[13], 4);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xeaa127fa + M[0], 11);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xd4ef3085 + M[3], 16);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x04881d05 + M[6], 23);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xd9d4d039 + M[9], 4);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xe6db99e5 + M[12], 11);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x1fa27cf8 + M[15], 16);
  a = temp;
  f = b ^ c ^ d;
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xc4ac5665 + M[2], 23);
  a = temp;

  // round 4
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xf4292244 + M[0], 6);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x432aff97 + M[7], 10);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xab9423a7 + M[14], 15);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xfc93a039 + M[5], 21);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x655b59c3 + M[12], 6);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x8f0ccc92 + M[3], 10);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xffeff47d + M[10], 15);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x85845dd1 + M[1], 21);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x6fa87e4f + M[8], 6);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xfe2ce6e0 + M[15], 10);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xa3014314 + M[6], 15);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x4e0811a1 + M[13], 21);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xf7537e82 + M[4], 6);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xbd3af235 + M[11], 10);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0x2ad7d2bb + M[2], 15);
  a = temp;
  f = c ^ (b | ~d);
  temp = d;
  d = c;
  c = b;
  b += left_rotate (a + f + 0xeb86d391 + M[9], 21);
  a = temp;

  // update keys
  a_ += a;
  b_ += b;
  c_ += c;
  d_ += d;

  // update size
  size_ += data.size ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief generate final hash value
//! \param data remaining data
//! \return digest
// the final block is padded with a 1 bit, \0 bytes and a 64-bit value
// representing the message size in bits.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::bytearray
hash_md5::_evaluate (const mobius::bytearray& data)
{
  hash_md5 tmp (*this);

  // represent message size in bits as a bytearray
  std::uint64_t bits = ((size_ + data.size ()) << 3);

  mobius::bytearray dsize =
  {
    std::uint8_t (bits),
    std::uint8_t (bits >> 8),
    std::uint8_t (bits >> 16),
    std::uint8_t (bits >> 24),
    std::uint8_t (bits >> 32),
    std::uint8_t (bits >> 40),
    std::uint8_t (bits >> 48),
    std::uint8_t (bits >> 56)
  };

  // if the length of the last block size >= 56, generate two blocks:
  // one: data + '1' bit + '0' bits (padding)
  // two: 56 bytes (0) + message size
  if (data.size () >= 56)
    {
      mobius::bytearray padding (64 - data.size ());
      padding.fill (0);
      padding[0] = 0x80;                // 1st bit = "1"
      _update_block (data + padding);

      mobius::bytearray padding2 (56);
      padding2.fill (0);
      _update_block (padding2 + dsize);
    }

  // otherwise, generate just one block:
  // data + '1' bit + '0' bits (padding) + message size
  else
    {
      mobius::bytearray padding (56 - data.size ());
      padding.fill (0);
      padding[0] = 0x80;                // 1st bit = "1"
      _update_block (data + padding + dsize);
    }

  // build digest
  mobius::bytearray digest
  {
    std::uint8_t (a_),
    std::uint8_t (a_ >> 8),
    std::uint8_t (a_ >> 16),
    std::uint8_t (a_ >> 24),
    std::uint8_t (b_),
    std::uint8_t (b_ >> 8),
    std::uint8_t (b_ >> 16),
    std::uint8_t (b_ >> 24),
    std::uint8_t (c_),
    std::uint8_t (c_ >> 8),
    std::uint8_t (c_ >> 16),
    std::uint8_t (c_ >> 24),
    std::uint8_t (d_),
    std::uint8_t (d_ >> 8),
    std::uint8_t (d_ >> 16),
    std::uint8_t (d_ >> 24)
  };

  *this = tmp;
  return digest;
}

} // namespace crypt
} // namespace mobius
