/*

Copyright 2007 Andy Tompkins.

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

// This is a byte oriented implementation
// Note: this implementation does not handle message longer than
//       2^32 bytes.

#ifndef LIB_SHA1_H
#define LIB_SHA1_H

#include <cstddef>

static_assert(sizeof(unsigned char)*8 == 8, "Please reboot the C universe.");
static_assert(sizeof(unsigned int)*8 == 32, "sizeof(unsigned int) == 4");

#define SHA_DIGEST_LENGTH 20

class sha1
{
public:
    typedef unsigned int(&digest_type)[5];
public:
    sha1();

    void reset();

    void process_byte(unsigned char byte);
    void process_block(void const* bytes_begin, void const* bytes_end);
    void process_bytes(void const* buffer, std::size_t byte_count);

    void get_digest(digest_type digest);

private:
    void process_block();
    static unsigned int left_rotate(unsigned int x, std::size_t n)
    {
        return (x<<n) ^ (x>> (32-n));
    }

private:
    unsigned int h_[5];

    unsigned char block_[64];

    std::size_t block_byte_index_;
    std::size_t byte_count_;
};

inline sha1::sha1()
{
    reset();
}

inline void sha1::reset()
{
    h_[0] = 0x67452301;
    h_[1] = 0xEFCDAB89;
    h_[2] = 0x98BADCFE;
    h_[3] = 0x10325476;
    h_[4] = 0xC3D2E1F0;

    block_byte_index_ = 0;
    byte_count_ = 0;
}

inline void sha1::process_byte(unsigned char byte)
{
    block_[block_byte_index_++] = byte;
    ++byte_count_;
    if (block_byte_index_ == 64) {
        block_byte_index_ = 0;
        process_block();
    }
}

inline void sha1::process_block(void const* bytes_begin, void const* bytes_end)
{
    unsigned char const* begin = static_cast<unsigned char const*>(bytes_begin);
    unsigned char const* end = static_cast<unsigned char const*>(bytes_end);
    for(; begin != end; ++begin) {
        process_byte(*begin);
    }
}

inline void sha1::process_bytes(void const* buffer, std::size_t byte_count)
{
    unsigned char const* b = static_cast<unsigned char const*>(buffer);
    process_block(b, b+byte_count);
}

inline void sha1::process_block()
{
    unsigned int w[80];
    for (std::size_t i=0; i<16; ++i) {
        w[i]  = (block_[i*4 + 0] << 24);
        w[i] |= (block_[i*4 + 1] << 16);
        w[i] |= (block_[i*4 + 2] << 8);
        w[i] |= (block_[i*4 + 3]);
    }
    for (std::size_t i=16; i<80; ++i) {
        w[i] = left_rotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1);
    }

    unsigned int a = h_[0];
    unsigned int b = h_[1];
    unsigned int c = h_[2];
    unsigned int d = h_[3];
    unsigned int e = h_[4];

    for (std::size_t i=0; i<80; ++i) {
        unsigned int f;
        unsigned int k;

        if (i<20) {
            f = (b & c) | (~b & d);
            k = 0x5A827999;
        } else if (i<40) {
            f = b ^ c ^ d;
            k = 0x6ED9EBA1;
        } else if (i<60) {
            f = (b & c) | (b & d) | (c & d);
            k = 0x8F1BBCDC;
        } else {
            f = b ^ c ^ d;
            k = 0xCA62C1D6;
        }

        unsigned temp = left_rotate(a, 5) + f + e + k + w[i];
        e = d;
        d = c;
        c = left_rotate(b, 30);
        b = a;
        a = temp;
    }

    h_[0] += a;
    h_[1] += b;
    h_[2] += c;
    h_[3] += d;
    h_[4] += e;
}

inline void sha1::get_digest(digest_type digest)
{
    std::size_t bit_count = byte_count_*8;

    // append the bit '1' to the message
    process_byte(0x80);

    // append k bits '0', where k is the minimum number >= 0
    // such that the resulting message length is congruent to 56 (mod 64)
    // check if there is enough space for padding and bit_count
    if (block_byte_index_ > 56) {
        // finish this block
        while (block_byte_index_ != 0) {
            process_byte(0);
        }

        // one more block
        while (block_byte_index_ < 56) {
            process_byte(0);
        }
    } else {
        while (block_byte_index_ < 56) {
            process_byte(0);
        }
    }

    // append length of message (before pre-processing) 
    // as a 64-bit big-endian integer
    process_byte(0);
    process_byte(0);
    process_byte(0);
    process_byte(0);
    process_byte( static_cast<unsigned char>((bit_count>>24) & 0xFF));
    process_byte( static_cast<unsigned char>((bit_count>>16) & 0xFF));
    process_byte( static_cast<unsigned char>((bit_count>>8 ) & 0xFF));
    process_byte( static_cast<unsigned char>((bit_count)     & 0xFF));

    // get final digest
    digest[0] = h_[0];
    digest[1] = h_[1];
    digest[2] = h_[2];
    digest[3] = h_[3];
    digest[4] = h_[4];
}

#endif
