//
//
//This file contains an implementation of the MD4 hash algorithm.
//The implementation was derived from the example source code that
//is provided in RFC 1320 - "The MD4 Message-Digest Algorithm".
//
//The code has been ported from C to C#.
//The original copyright is preserved here.
//
//
// Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
// rights reserved.
//
// License to copy and use this software is granted provided that it
// is identified as the "RSA Data Security, Inc. MD4 Message-Digest
// Algorithm" in all material mentioning or referencing this software
// or this function.
//
// License is also granted to make and use derivative works provided
// that such works are identified as "derived from the RSA Data
// Security, Inc. MD4 Message-Digest Algorithm" in all material
// mentioning or referencing the derived work.
//
// RSA Data Security, Inc. makes no representations concerning either
// the merchantability of this software or the suitability of this
// software for any particular purpose. It is provided "as is"
// without express or implied warranty of any kind.
//
// These notices must be retained in any copies of any part of this
// documentation and/or software.
//
//

using System;
using System.Diagnostics;
using System.Text;

namespace System.Security.Cryptography
{

    /// <summary>
    /// This class implements the MD4 message digest algorithm.
    /// The implementation is a C# port of the original RSA C implementation that is
    /// provided in RFC 1320.
    /// </summary>
    public class MD4Context
    {
        public MD4Context()
        {
            Reset();
        }

        public const int DigestLength = 16;

        /// <summary>
        /// This field is "true" if GetDigest has been called.
        /// It cannot be called again, unless Reset is called.
        /// </summary>
        bool _done;

        public void Clear()
        {
            _state0 = 0;
            _state1 = 0;
            _state2 = 0;
            _state3 = 0;
            _count = 0;
            for (int i = 0; i < 64; i++)
                _buffer[i] = 0;
        }

        /// <summary>
        /// This method can be used to reset the state of the MD4 context.  After returning, the
        /// state of the MD4Context is equivalent to its state immediately after the constructor
        /// finished.
        /// </summary>
        public void Reset()
        {
            // Load magic initialization constants.
            _count = 0;
            _state0 = 0x67452301;
            _state1 = 0xefcdab89;
            _state2 = 0x98badcfe;
            _state3 = 0x10325476;
            _done = false;
        }

        //
        //MD4 basic transformation. Transforms state based on block.
        // 
        void Transform(byte[]/*!*/ block, int offset)
        {
            if (offset < 0)
                throw new ArgumentException("offset cannot be negative.");

            Decode(_block, 0, block, offset, 64);
            Transform(_block);

            // Zeroize sensitive information.
            // MD4_memset ((POINTER)x, 0, sizeof (x));
        }

        #region Constants for MD4Transform routine.
        const int S11 = 3;
        const int S12 = 7;
        const int S13 = 11;
        const int S14 = 19;
        const int S21 = 3;
        const int S22 = 5;
        const int S23 = 9;
        const int S24 = 13;
        const int S31 = 3;
        const int S32 = 9;
        const int S33 = 11;
        const int S34 = 15;
        #endregion

        /// <summary>
        /// This routine transforms the current MD4 context, using a block of input.
        /// The input is 16 words, where each word is 32 unsigned bits.  This method
        /// is the core of the MD4 algorithm.
        /// </summary>
        /// <param name="x">The message data to use to transform the context.</param>
        void Transform(uint[] x)
        {
            uint a = _state0;
            uint b = _state1;
            uint c = _state2;
            uint d = _state3;

            // Round 1   
            a = FF(a, b, c, d, x[00], S11); // 1   
            d = FF(d, a, b, c, x[01], S12); // 2   
            c = FF(c, d, a, b, x[02], S13); // 3   
            b = FF(b, c, d, a, x[03], S14); // 4   
            a = FF(a, b, c, d, x[04], S11); // 5   
            d = FF(d, a, b, c, x[05], S12); // 6   
            c = FF(c, d, a, b, x[06], S13); // 7   
            b = FF(b, c, d, a, x[07], S14); // 8   
            a = FF(a, b, c, d, x[08], S11); // 9   
            d = FF(d, a, b, c, x[09], S12); // 10   
            c = FF(c, d, a, b, x[10], S13); // 11   
            b = FF(b, c, d, a, x[11], S14); // 12   
            a = FF(a, b, c, d, x[12], S11); // 13   
            d = FF(d, a, b, c, x[13], S12); // 14   
            c = FF(c, d, a, b, x[14], S13); // 15   
            b = FF(b, c, d, a, x[15], S14); // 16   

            // Round 2   
            a = GG(a, b, c, d, x[00], S21); // 17   
            d = GG(d, a, b, c, x[04], S22); // 18   
            c = GG(c, d, a, b, x[08], S23); // 19   
            b = GG(b, c, d, a, x[12], S24); // 20   
            a = GG(a, b, c, d, x[01], S21); // 21   
            d = GG(d, a, b, c, x[05], S22); // 22   
            c = GG(c, d, a, b, x[09], S23); // 23   
            b = GG(b, c, d, a, x[13], S24); // 24   
            a = GG(a, b, c, d, x[02], S21); // 25   
            d = GG(d, a, b, c, x[06], S22); // 26   
            c = GG(c, d, a, b, x[10], S23); // 27   
            b = GG(b, c, d, a, x[14], S24); // 28   
            a = GG(a, b, c, d, x[03], S21); // 29   
            d = GG(d, a, b, c, x[07], S22); // 30   
            c = GG(c, d, a, b, x[11], S23); // 31   
            b = GG(b, c, d, a, x[15], S24); // 32   

            // Round 3   
            a = HH(a, b, c, d, x[00], S31); // 33   
            d = HH(d, a, b, c, x[08], S32); // 34   
            c = HH(c, d, a, b, x[04], S33); // 35   
            b = HH(b, c, d, a, x[12], S34); // 36   
            a = HH(a, b, c, d, x[02], S31); // 37   
            d = HH(d, a, b, c, x[10], S32); // 38   
            c = HH(c, d, a, b, x[06], S33); // 39   
            b = HH(b, c, d, a, x[14], S34); // 40   
            a = HH(a, b, c, d, x[01], S31); // 41   
            d = HH(d, a, b, c, x[09], S32); // 42   
            c = HH(c, d, a, b, x[05], S33); // 43   
            b = HH(b, c, d, a, x[13], S34); // 44   
            a = HH(a, b, c, d, x[03], S31); // 45   
            d = HH(d, a, b, c, x[11], S32); // 46   
            c = HH(c, d, a, b, x[07], S33); // 47   
            b = HH(b, c, d, a, x[15], S34); // 48   

            _state0 = unchecked(_state0 + a);
            _state1 = unchecked(_state1 + b);
            _state2 = unchecked(_state2 + c);
            _state3 = unchecked(_state3 + d);
        }

        public const int BytesPerTransform = 64;
        public const int WordsPerTransform = 16;

        uint _state0;             // state A
        uint _state1;             // state B
        uint _state2;             // state C
        uint _state3;             // state D
        Int64 _count;              // number of bits, modulo 2^64

        /// <summary>
        /// This buffer holds data that was submitted using the Update method, but which
        /// was too short to complete a full transform block (64 bytes).
        /// </summary>
        readonly byte[]/*!*/ _buffer = new byte[BytesPerTransform];

        /// <summary>
        /// This buffer contains the current block (of message data) that is being transformed.
        /// </summary>
        readonly uint[]/*!*/ _block = new uint[WordsPerTransform];

        static MD4Context()
        {
            PADDING = new byte[BytesPerTransform];
            Array.Clear(PADDING, 0, 64);
            PADDING[0] = 0x80;
        }

        static readonly byte[]/*!*/ PADDING;
        //
        //{
        //  0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        //      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        //      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
        //};
        //

        // F, G and H are basic MD4 functions.
        //
        static uint F(uint x, uint y, uint z) { return (x & y) | ((~x) & z); }
        static uint G(uint x, uint y, uint z) { return (x & y) | (x & z) | (y & z); }
        static uint H(uint x, uint y, uint z) { return x ^ y ^ z; }

        // ROTATE_LEFT rotates x left n bits.
        //
        static uint RotateLeft(uint x, int n)
        {
            return (x << n) | (x >> (32 - n));
        }

        // FF, GG and HH are transformations for rounds 1, 2 and 3   
        // Rotation is separate from addition to prevent recomputation   
        // (Or at least it used to be, when these were C macros.   

        static uint FF(uint a, uint b, uint c, uint d, uint x, int s)
        {
            a = unchecked(a + F(b, c, d) + x);
            return RotateLeft(a, s);
        }

        static uint GG(uint a, uint b, uint c, uint d, uint x, int s)
        {
            a = unchecked(a + G(b, c, d) + x + (uint)0x5a827999);
            return RotateLeft(a, s);
        }

        static uint HH(uint a, uint b, uint c, uint d, uint x, int s)
        {
            a = unchecked(a + H(b, c, d) + x + (uint)0x6ed9eba1);
            return RotateLeft(a, s);
        }


        // Decodes input (unsigned char) into output (uint). Assumes len is
        //   a multiple of 4.
//
        // 

        /// <summary>
        /// 
        /// </summary>
        /// <param name="output"></param>
        /// <param name="outputoffset"></param>
        /// <param name="input"></param>
        /// <param name="inputoffset"></param>
        /// <param name="length">number of BYTES to transfer</param>
        static void Decode(uint[]/*!*/ output, int outputoffset, byte[]/*!*/ input, int inputoffset, int length)
        {
            Debug.Assert(length % 4 == 0);

            int outpos = outputoffset;

            for (int j = 0; j < length; j += 4) {
                uint value = ((uint)input[inputoffset + j])
                    | (((uint)input[inputoffset + j + 1]) << 8)
                    | (((uint)input[inputoffset + j + 2]) << 16)
                    | (((uint)input[inputoffset + j + 3]) << 24);
                output[outpos] = value;

                outpos++;
            }
        }

        static void EncodeLe(uint value, byte[]/*!*/ output, int pos)
        {
            output[pos] = (byte)(value & 0xff);
            output[pos + 1] = (byte)((value >> 8) & 0xff);
            output[pos + 2] = (byte)((value >> 16) & 0xff);
            output[pos + 3] = (byte)((value >> 24) & 0xff);
        }

        /// <summary>
        /// MD4 block update operation. Continues an MD4 message-digest operation,
        /// processing another message block, and updating the context.
        /// </summary>
        /// <param name="input">The buffer containing data to process.</param>
        /// <param name="offset">The offset within the buffer where the data begins.</param>
        /// <param name="length">The length of the data.</param>
        public void Update(byte[]/*!*/ input, int offset, int length)
        {
            if (_done)
                throw new InvalidOperationException("The hash context has been closed (GetDigest has been called).  It cannot be reused until Reset is called.");

            if (length == 0)
                return;

            // Compute number of bytes mod 64   
            int index = (int)((_count >> 3) & 0x3F);
            _count += length << 3;

            int partLen = 64 - index;

            // Transform as many times as possible.
            //
            int i;
            if (length >= partLen) {
                // MD4_memcpy((POINTER)&buffer_pinned[index], (POINTER)&input[offset], partLen);
                Array.Copy(input, offset, _buffer, index, partLen);
                Transform(_buffer, 0);

                for (i = partLen; i + 63 < length; i += 64)
                    Transform(input, offset + i);

                index = 0;
            }
            else
                i = 0;

            // Buffer remaining input   
            // MD4_memcpy((POINTER)&_buffer[index], (POINTER)&input[i], inputLen-i);
            // MD4_memcpy((POINTER)&buffer_pinned[index], (POINTER)&input[offset + i], inputLen-i);
            Array.Copy(input, offset + i, _buffer, index, length - i);
        }


        public static MD4Digest GetDigest(byte[]/*!*/ buffer)
        {
            return GetDigest(buffer, 0, buffer.Length);
        }

        public static MD4Digest GetDigest(byte[]/*!*/ buffer, int offset, int length)
        {
            MD4Context context = new MD4Context();
            context.Update(buffer, offset, length);
            return context.GetDigest();
        }

        // MD4 finalization. Ends an MD4 message-digest operation, writing the
        //   the message digest and zeroizing the context.
        // 
        public MD4Digest GetDigest()
        {
            if (_done)
                throw new InvalidOperationException("GetDigest cannot be called twice for a single hash sequence.  Call Reset() to reset the context.");

            byte[]/*!*/ bits = new byte[8];

            // Save number of bits   
            EncodeLe((uint)(_count & 0xffffffffu), bits, 0);
            EncodeLe((uint)(_count >> 32), bits, 4);

            // Pad out to 56 mod 64.
            int index = (int)((_count >> 3) & 0x3f);
            int padLen = (index < 56) ? (56 - index) : (120 - index);
            Update(PADDING, 0, padLen);

            // Append length (before padding)
            Update(bits, 0, 8);

            // Store state in digest

            MD4Digest digest;
            digest.state0 = _state0;
            digest.state1 = _state1;
            digest.state2 = _state2;
            digest.state3 = _state3;

            Clear();

            _done = true;

            return digest;
        }
    }

    public struct MD4Digest
    {
        public uint state0;
        public uint state1;
        public uint state2;
        public uint state3;

        static void PackLe(byte[]/*!*/ dest, int offset, uint value)
        {
            dest[offset + 0] = (byte)((value) & 0xff);
            dest[offset + 1] = (byte)((value >> 8) & 0xff);
            dest[offset + 2] = (byte)((value >> 16) & 0xff);
            dest[offset + 3] = (byte)((value >> 24) & 0xff);
        }

        public const int DigestLength = 16;

        public byte[]/*!*/ ToArray()
        {
            byte[] result = new byte[DigestLength];
            ToArray(result);
            return result;
        }

        public void ToArray(byte[]/*!*/ result)
        {
            if (result.Length < 0x10)
                throw new ArgumentException("Input array is too short.");

            PackLe(result, 0, state0);
            PackLe(result, 4, state1);
            PackLe(result, 8, state2);
            PackLe(result, 12, state3);
        }

        public static implicit operator byte[]/*!*/(MD4Digest digest)
        {
            return digest.ToArray();
        }

        override public string/*!*/ ToString()
        {
            string hex = "0123456789abcdef";
            byte[] arr = ToArray();
            StringBuilder buffer = new StringBuilder(DigestLength * 2);
            for (int i = 0; i < 0x10; i++) {
                byte b = arr[i];
                buffer.Append(hex[b >> 4]);
                buffer.Append(hex[b & 0xf]);
            }
            return buffer.ToString();
        }
    }

}
