/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     | Website:  https://openfoam.org
    \\  /    A nd           | Copyright (C) 2011-2021 OpenFOAM Foundation
     \\/     M anipulation  |
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM 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 3 of the License, or
    (at your option) any later version.

    OpenFOAM 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 OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

Description
    Functions to compute SHA1 message digest of files or memory blocks
    according to the NIST specification FIPS-180-1.

    Adapted from the gnulib implementation written by Scott G. Miller with
    credits to Robert Klep <robert@ilse.nl> -- Expansion function fix

    Copyright (C) 2000, 2001, 2003, 2004, 2005, 2006, 2008 Free Software
    Foundation, Inc.

\*---------------------------------------------------------------------------*/

#include "SHA1.H"
#include "IOstreams.H"

#include <cstring>

#if defined (__GLIBC__)
    #include <endian.h>
#endif


// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

//! \cond fileScope
//  The bytes used to pad buffer to the next 64-byte boundary.
//  (RFC 1321, 3.1: Step 1)
static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ...  */ };
//! \endcond


// * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * * //

inline uint32_t Foam::SHA1::swapBytes(uint32_t n)
{
    #ifdef __BYTE_ORDER
        #if (__BYTE_ORDER == __BIG_ENDIAN)
        return n;
        #else
        return
        (
            ((n) << 24)
          | (((n) & 0xff00) << 8)
          | (((n) >> 8) & 0xff00)
          | ((n) >> 24)
        );
        #endif
    #else
        const short x = 0x0100;

        // yields 0x01 for big endian
        if (*(reinterpret_cast<const char*>(&x)))
        {
            return n;
        }
        else
        {
            return
            (
                ((n) << 24)
              | (((n) & 0xff00) << 8)
              | (((n) >> 8) & 0xff00)
              | ((n) >> 24)
            );
        }
    #endif
}


inline void Foam::SHA1::set_uint32(unsigned char *cp, uint32_t v)
{
    memcpy(cp, &v, sizeof(uint32_t));
}


// * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * * //

void Foam::SHA1::processBytes(const void *data, size_t len)
{
    // already finalised, thus need to restart from nothing
    if (finalised_)
    {
        clear();
    }

    // complete filling of internal buffer
    if (bufLen_)
    {
        size_t remaining = bufLen_;
        size_t add =
        (
            sizeof(buffer_) - remaining > len
          ? len
          : sizeof(buffer_) - remaining
        );

        unsigned char* bufp = reinterpret_cast<unsigned char*>(buffer_);

        memcpy(&bufp[remaining], data, add);
        bufLen_ += add;

        if (bufLen_ > 64)
        {
            processBlock(buffer_, bufLen_ & ~63);

            bufLen_ &= 63;
            // The regions in the following copy operation do not
            // (cannot) overlap
            memcpy(buffer_, &bufp[(remaining + add) & ~63], bufLen_);
        }

        data = reinterpret_cast<const unsigned char*>(data) + add;
        len -= add;
    }

    // Process available complete blocks
    while (len >= 64)
    {
        processBlock(memcpy(buffer_, data, 64), 64);
        data = reinterpret_cast<const unsigned char*>(data) + 64;
        len -= 64;
    }

    // Move remaining bytes in internal buffer.
    if (len > 0)
    {
        unsigned char* bufp = reinterpret_cast<unsigned char*>(buffer_);
        size_t remaining = bufLen_;

        memcpy (&bufp[remaining], data, len);
        remaining += len;
        if (remaining >= 64)
        {
            processBlock(buffer_, 64);
            remaining -= 64;
            memcpy(buffer_, &buffer_[16], remaining);
        }
        bufLen_ = remaining;
    }
}


// SHA1 round constants
#define K1 0x5a827999
#define K2 0x6ed9eba1
#define K3 0x8f1bbcdc
#define K4 0xca62c1d6

// Round functions.  Note that F2 is the same as F4.
#define F1(B,C,D) ( D ^ ( B & ( C ^ D ) ) )
#define F2(B,C,D) (B ^ C ^ D)
#define F3(B,C,D) ( ( B & C ) | ( D & ( B | C ) ) )
#define F4(B,C,D) (B ^ C ^ D)

// Process LEN bytes of BUFFER, it is assumed that LEN % 64 == 0.
// Most of this code comes from GnuPG's cipher/sha1.c

void Foam::SHA1::processBlock(const void *data, size_t len)
{
    const uint32_t *words = reinterpret_cast<const uint32_t*>(data);
    size_t nwords = len / sizeof(uint32_t);
    const uint32_t *endp = words + nwords;

    // calculate with sixteen words of 32-bits
    uint32_t x[16];
    uint32_t a = hashsumA_;
    uint32_t b = hashsumB_;
    uint32_t c = hashsumC_;
    uint32_t d = hashsumD_;
    uint32_t e = hashsumE_;

    // First increment the byte count.
    // RFC 1321 specifies the possible length of the file up to 2^64 bits.
    // Here we only compute the number of bytes.  Do a double word increment.
    bufTotal_[0] += len;
    if (bufTotal_[0] < len)
    {
        ++bufTotal_[1];
    }

    // rotate left uint32_t by n bits
    #define rol_uint32(x, nbits)  (((x) << (nbits)) | ((x) >> (32 - (nbits))))

    #define M(I) ( tm = x[I & 0x0F] ^ x[(I-14) & 0x0F]                         \
               ^ x[(I-8) & 0x0F] ^ x[(I-3) & 0x0F]                             \
               , (x[I & 0x0F] = rol_uint32(tm, 1)) )

    #define R(A,B,C,D,E,F,K,M)                                                 \
        do                                                                     \
        {                                                                      \
            E += rol_uint32(A, 5) + F(B, C, D) + K + M;                        \
            B = rol_uint32(B, 30);                                             \
        } while (0)

    while (words < endp)
    {
        uint32_t tm;
        for (int t = 0; t < 16; ++t)
        {
            x[t] = swapBytes(*words);
            ++words;
        }

        R( a, b, c, d, e, F1, K1, x[ 0] );
        R( e, a, b, c, d, F1, K1, x[ 1] );
        R( d, e, a, b, c, F1, K1, x[ 2] );
        R( c, d, e, a, b, F1, K1, x[ 3] );
        R( b, c, d, e, a, F1, K1, x[ 4] );
        R( a, b, c, d, e, F1, K1, x[ 5] );
        R( e, a, b, c, d, F1, K1, x[ 6] );
        R( d, e, a, b, c, F1, K1, x[ 7] );
        R( c, d, e, a, b, F1, K1, x[ 8] );
        R( b, c, d, e, a, F1, K1, x[ 9] );
        R( a, b, c, d, e, F1, K1, x[10] );
        R( e, a, b, c, d, F1, K1, x[11] );
        R( d, e, a, b, c, F1, K1, x[12] );
        R( c, d, e, a, b, F1, K1, x[13] );
        R( b, c, d, e, a, F1, K1, x[14] );
        R( a, b, c, d, e, F1, K1, x[15] );
        R( e, a, b, c, d, F1, K1, M(16) );
        R( d, e, a, b, c, F1, K1, M(17) );
        R( c, d, e, a, b, F1, K1, M(18) );
        R( b, c, d, e, a, F1, K1, M(19) );
        R( a, b, c, d, e, F2, K2, M(20) );
        R( e, a, b, c, d, F2, K2, M(21) );
        R( d, e, a, b, c, F2, K2, M(22) );
        R( c, d, e, a, b, F2, K2, M(23) );
        R( b, c, d, e, a, F2, K2, M(24) );
        R( a, b, c, d, e, F2, K2, M(25) );
        R( e, a, b, c, d, F2, K2, M(26) );
        R( d, e, a, b, c, F2, K2, M(27) );
        R( c, d, e, a, b, F2, K2, M(28) );
        R( b, c, d, e, a, F2, K2, M(29) );
        R( a, b, c, d, e, F2, K2, M(30) );
        R( e, a, b, c, d, F2, K2, M(31) );
        R( d, e, a, b, c, F2, K2, M(32) );
        R( c, d, e, a, b, F2, K2, M(33) );
        R( b, c, d, e, a, F2, K2, M(34) );
        R( a, b, c, d, e, F2, K2, M(35) );
        R( e, a, b, c, d, F2, K2, M(36) );
        R( d, e, a, b, c, F2, K2, M(37) );
        R( c, d, e, a, b, F2, K2, M(38) );
        R( b, c, d, e, a, F2, K2, M(39) );
        R( a, b, c, d, e, F3, K3, M(40) );
        R( e, a, b, c, d, F3, K3, M(41) );
        R( d, e, a, b, c, F3, K3, M(42) );
        R( c, d, e, a, b, F3, K3, M(43) );
        R( b, c, d, e, a, F3, K3, M(44) );
        R( a, b, c, d, e, F3, K3, M(45) );
        R( e, a, b, c, d, F3, K3, M(46) );
        R( d, e, a, b, c, F3, K3, M(47) );
        R( c, d, e, a, b, F3, K3, M(48) );
        R( b, c, d, e, a, F3, K3, M(49) );
        R( a, b, c, d, e, F3, K3, M(50) );
        R( e, a, b, c, d, F3, K3, M(51) );
        R( d, e, a, b, c, F3, K3, M(52) );
        R( c, d, e, a, b, F3, K3, M(53) );
        R( b, c, d, e, a, F3, K3, M(54) );
        R( a, b, c, d, e, F3, K3, M(55) );
        R( e, a, b, c, d, F3, K3, M(56) );
        R( d, e, a, b, c, F3, K3, M(57) );
        R( c, d, e, a, b, F3, K3, M(58) );
        R( b, c, d, e, a, F3, K3, M(59) );
        R( a, b, c, d, e, F4, K4, M(60) );
        R( e, a, b, c, d, F4, K4, M(61) );
        R( d, e, a, b, c, F4, K4, M(62) );
        R( c, d, e, a, b, F4, K4, M(63) );
        R( b, c, d, e, a, F4, K4, M(64) );
        R( a, b, c, d, e, F4, K4, M(65) );
        R( e, a, b, c, d, F4, K4, M(66) );
        R( d, e, a, b, c, F4, K4, M(67) );
        R( c, d, e, a, b, F4, K4, M(68) );
        R( b, c, d, e, a, F4, K4, M(69) );
        R( a, b, c, d, e, F4, K4, M(70) );
        R( e, a, b, c, d, F4, K4, M(71) );
        R( d, e, a, b, c, F4, K4, M(72) );
        R( c, d, e, a, b, F4, K4, M(73) );
        R( b, c, d, e, a, F4, K4, M(74) );
        R( a, b, c, d, e, F4, K4, M(75) );
        R( e, a, b, c, d, F4, K4, M(76) );
        R( d, e, a, b, c, F4, K4, M(77) );
        R( c, d, e, a, b, F4, K4, M(78) );
        R( b, c, d, e, a, F4, K4, M(79) );

        a = hashsumA_ += a;
        b = hashsumB_ += b;
        c = hashsumC_ += c;
        d = hashsumD_ += d;
        e = hashsumE_ += e;
    }
}


void Foam::SHA1::calcDigest(SHA1Digest& dig) const
{
    if (bufTotal_[0] || bufTotal_[1])
    {
        unsigned char *r = dig.v_;

        set_uint32(r + 0 * sizeof(uint32_t), swapBytes(hashsumA_));
        set_uint32(r + 1 * sizeof(uint32_t), swapBytes(hashsumB_));
        set_uint32(r + 2 * sizeof(uint32_t), swapBytes(hashsumC_));
        set_uint32(r + 3 * sizeof(uint32_t), swapBytes(hashsumD_));
        set_uint32(r + 4 * sizeof(uint32_t), swapBytes(hashsumE_));
    }
    else
    {
        // no data!
        dig.clear();
    }
}


// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //

void Foam::SHA1::clear()
{
    hashsumA_ = 0x67452301;
    hashsumB_ = 0xefcdab89;
    hashsumC_ = 0x98badcfe;
    hashsumD_ = 0x10325476;
    hashsumE_ = 0xc3d2e1f0;

    bufTotal_[0] = bufTotal_[1] = 0;
    bufLen_ = 0;

    finalised_ = false;
}


bool Foam::SHA1::finalise()
{
    if (!finalised_)
    {
        finalised_ = true;

        // account for unprocessed bytes
        uint32_t bytes = bufLen_;
        size_t size = (bytes < 56 ? 64 : 128) / sizeof(uint32_t);

        // count remaining bytes.
        bufTotal_[0] += bytes;
        if (bufTotal_[0] < bytes)
        {
            ++bufTotal_[1];
        }

        // finalised, but no data!
        if (!bufTotal_[0] && !bufTotal_[1])
        {
            return false;
        }

        // place the 64-bit file length in *bits* at the end of the buffer.
        buffer_[size-2] = swapBytes((bufTotal_[1] << 3) | (bufTotal_[0] >> 29));
        buffer_[size-1] = swapBytes(bufTotal_[0] << 3);

        unsigned char* bufp = reinterpret_cast<unsigned char *>(buffer_);

        memcpy(&bufp[bytes], fillbuf, (size-2) * sizeof(uint32_t) - bytes);

        // Process remaining bytes
        processBlock(buffer_, size * sizeof(uint32_t));
    }

    return true;
}


Foam::SHA1Digest Foam::SHA1::digest() const
{
    SHA1Digest dig;

    if (finalised_)
    {
        calcDigest(dig);
    }
    else
    {
        // avoid disturbing our data - use a copy
        SHA1 sha(*this);
        if (sha.finalise())
        {
            sha.calcDigest(dig);
        }
    }

    return dig;
}


// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

#undef K1
#undef K2
#undef K3
#undef K4

#undef F1
#undef F2
#undef F3
#undef F4

#undef rol_uint32
#undef M
#undef R

// ************************************************************************* //
