/* 
   This code was copied form Rsync by Craig Barratt

   Copyright (C) Andrew Tridgell 1996
   Copyright (C) Paul Mackerras 1996
   
   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 of the License, 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, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "global.h"
#include "md4.h"
#include <string.h>

/*
 * CHAR_OFFSET is 0 for rsync, and 31 for librsync.
 */
#define CHAR_OFFSET 0

/*
 * a simple 32 bit checksum that can be updated from either end
 * (inspired by Mark Adler's Adler-32 checksum)
 */
UINT4 adler32_checksum(char *buf, int len)
{
    int i;
    UINT4 s1, s2;

    s1 = s2 = 0;
    for ( i = 0 ; i < len - 4; i += 4 ) {
        s2 += 4 * (s1 + buf[i]) + 3 * buf[i+1] + 2 * buf[i+2] + buf[i+3] +
	      10 * CHAR_OFFSET;
        s1 += (buf[i+0] + buf[i+1] + buf[i+2] + buf[i+3] + 4 * CHAR_OFFSET);
    }
    for ( ; i < len ; i++ ) {
        s1 += (buf[i] + CHAR_OFFSET);
	s2 += s1;
    }
    return (s1 & 0xffff) + (s2 << 16);
}

/*
 * Compute both the alder32 and MD4 checksums for blockSize sized
 * blocks from a buffer buf of length len.  Seed is the optional
 * Rsync seed that is appended to the data.  Each block produces
 * 4 + min(md4DigestLen,16) bytes of output (alder32+MD4) in digest.
 * The number of blocks is ceil(len/blockSize).
 *
 * There are two special cases:
 *   md4DigestLen == 0: skip MD4; output has adler32 only.
 *   md4DigestLen > 16: output MD4 is really MD4 state, prior to
 *                      MD4FinalRsync().
 */
void rsync_checksum(unsigned char *buf, UINT4 len, UINT4 blockSize, UINT4 seed,
    unsigned char *digest, int md4DigestLen)
{
    unsigned char seedBytes[4];

    if ( md4DigestLen > 0 && seed ) {
	RsyncMD4Encode(seedBytes, &seed, 1);
    }
    while ( len > 0 ) {
	int thisLen = len < blockSize ? len : blockSize;
	UINT4 adler32 = adler32_checksum((char*)buf, thisLen);

	RsyncMD4Encode(digest, &adler32, 1);
	digest += 4;
	if ( md4DigestLen ) {
	    RsyncMD4_CTX md4;
	    RsyncMD4Init(&md4);
	    RsyncMD4Update(&md4, buf, thisLen);
	    if ( seed ) {
		RsyncMD4Update(&md4, seedBytes, 4);
	    }
	    if ( md4DigestLen < 0 ) {
		/*
		 * Done: just save the state and the partial buffer (no finish)
		 */
	        RsyncMD4Encode(digest, md4.state, 16);
		digest += 16;
                memcpy(digest, md4.buffer, thisLen % 64);
                digest += thisLen % 64;
	    } else if ( md4DigestLen >= 16 ) {
		/*
		 * Normal finish: save all 16 bytes
		 */
		RsyncMD4FinalRsync(digest, &md4);
		digest += 16;
	    } else {
		unsigned char md4Digest[16];
		/*
		 * Finish and truncate to md4DigestLen bytes
		 */
		RsyncMD4FinalRsync(md4Digest, &md4);
		memcpy(digest, md4Digest, md4DigestLen);
		digest += md4DigestLen;
	    }
	}
	len -= thisLen;
	buf += thisLen;
    }
}

/*
 * Update the MD4 digest by adding the seed to the data.  Since
 * the rsync seed changes each time we need to add the seed.
 * We can do this by restoring the MD4 state (16 bytes plus
 * the length).  Each block has length blockSize, except the
 * last block, which is blockLastLen.
 *
 * The input data should be of length 20 * blockCnt.
 * The first block is blockStart (usually 0).  The length
 * of the last block is blockLastLen.  If seed == 0 then
 * it is skipped, and the MD4 digest is simply optionally
 * truncated.
 *
 * md4DigestLen is used to specify the MD4 digest length (eg: 2 or 16).
 * The output data size is blockCnt * (4 + md4DigestLen) bytes.
 */
void rsync_checksum_update(unsigned char *digestIn, UINT4 blockCnt,
    UINT4 blockSize, UINT4 blockLastLen, UINT4 seed,
    unsigned char *digestOut, int md4DigestLen)
{
    unsigned char seedBytes[4];

    if ( seed ) {
	RsyncMD4Encode(seedBytes, &seed, 1);
    }
    if ( md4DigestLen > 16 || md4DigestLen < 0 ) {
	md4DigestLen = 16;
    }
    while ( blockCnt-- ) {
        RsyncMD4_CTX md4;
	/*
	 * Copy adler32
	 */
	memcpy(digestOut, digestIn, 4);
	digestIn  += 4;
	digestOut += 4;
        RsyncMD4Init(&md4);
        RsyncMD4Decode(md4.state, digestIn, 16);
        digestIn  += 16;
        if ( blockCnt ) {
            md4.count[0] = blockSize << 3;
            md4.count[1] = blockSize >> 29;
            memcpy(md4.buffer, digestIn, blockSize % 64);
            digestIn += blockSize % 64;
        } else {
            md4.count[0] = blockLastLen << 3;
            md4.count[1] = blockLastLen >> 29;
            memcpy(md4.buffer, digestIn, blockLastLen % 64);
            digestIn += blockLastLen % 64;
        }
        if ( seed ) {
            RsyncMD4Update(&md4, seedBytes, 4);
        }
        if ( md4DigestLen == 16 ) {
            /*
             * Normal finish: save all 16 bytes
             */
            RsyncMD4FinalRsync(digestOut, &md4);
        } else {
            unsigned char md4Digest[16];
            /*
             * Finish and truncate to md4DigestLen bytes
             */
            RsyncMD4FinalRsync(md4Digest, &md4);
            memcpy(digestOut, md4Digest, md4DigestLen);
        }
        digestOut += md4DigestLen;
    }
}
