/* $Header$ */

/* Purpose: NCO utilities for MD5 digests */

/* Copyright (C) 1995--present Charlie Zender
   This file is part of NCO, the netCDF Operators. NCO is free software.
   You may redistribute and/or modify NCO under the terms of the 
   3-Clause BSD License with exceptions described in the LICENSE file */

/* Usage:
   ncecat -O -D 1 --md5 -p ~/nco/data in.nc in.nc ~/foo.nc
   ncrcat -O -D 1 --md5 -p ~/nco/data in.nc in.nc ~/foo.nc
   ncks -O -D 1 -H -C -m --md5 -v md5_a,md5_abc ~/nco/data/in.nc
   ncks -O -D 1 -H -C -m --md5 -v md5_a,md5_abc -p http://thredds-test.ucar.edu/thredds/dodsC/testdods in.nc
   ncks -O -D 1 -C -m --md5 -v md5_a,md5_abc,one_dmn_rec_var ~/nco/data/in.nc
   ncks -O -D 1 -C -d lev,0 -m --md5 -v md5_a,md5_abc,one_dmn_rec_var ~/nco/data/in.nc */

/* This NCO file contains the entirety of the MD5 implementation by
   L. Peter Deutsch of Aladdin Software (aka author of Ghostscript).
   NCO-specific functions are defined first.
   The NCO-copyright applies only to the NCO-specific functions.
   LPD's md5.c is included in a nearly unaltered state. 
   That code is covered by LPD's copyright. */

#include "nco_md5.h" /* MD5 digests */

md5_sct * /* [sct] MD5 configuration */
nco_md5_ini(void) /* [fnc] Initialize and return MD5 configuration structure */
{
  /* Purpose: Initialize and return MD5 configuration structure */
  md5_sct *md5; /* [sct] MD5 configuration */
  md5=(md5_sct *)nco_malloc(sizeof(md5_sct));
  md5->att_nm=(char *)strdup("MD5");
  md5->dgs=False;
  md5->wrt=False;
  return md5;
} /* end nco_md5_ini() */

md5_sct * /* [sct] MD5 configuration */
nco_md5_free /* [fnc] Free MD5 configuration structure */
(md5_sct *md5) /* [sct] MD5 configuration structure to free */
{
  /* Purpose: Initialize and return MD5 configuration structure */
  if(md5->att_nm) md5->att_nm=(char *)nco_free(md5->att_nm);
  if(md5) md5=(md5_sct *)nco_free(md5);
  return md5;
} /* end nco_md5_free() */

void
nco_md5_chk /* [fnc] Perform and optionally compare MD5 digest(s) on hyperslab */
(const md5_sct * const md5, /* I [sct] MD5 Configuration */
 const char * const var_nm, /* I [sng] Input variable name */
 const long var_sz_byt, /* I [nbr] Size (in bytes) of hyperslab in RAM */
 const int nc_id, /* I [id] netCDF file ID */
 const long * const dmn_srt, /* I [idx] Contiguous vector of indices to start of hyperslab on disk */
 const long * const dmn_cnt, /* I [nbr] Contiguous vector of lengths of hyperslab on disk */
 void * const vp) /* I/O [val] Values to digest */
{
  /* Purpose: Perform MD5 digest on hyperslab in RAM
     Optionally do same for hyperslab written to disk and compare with RAM digest 
     NB: Input argument var_sz_byt is independent of dmn_srt and dmn_cnt
     Routine uses var_sz_byt _only_ for MD5 digest of RAM variable 
     That is why this input is const
     Iff MD5 of disk hyperslab is requested, then routine uses nc_id and var_nm to obtain  
     var_id, dmn_nbr, var_typ_dsk of variable on disk.
     From these we obtain var_sz_dsk and var_sz_byt
     In other words, MD5(RAM) depends only on information provided in RAM while
     MD5(disk) utilizes netCDF layer to assemble hyperslab data.
     Idea is that this provides the best comparison of RAM vs. disk hyperslabs */

  int nco_prg_id; /* [enm] Program ID */
  
  char md5_dgs_hxd_sng_ram[NCO_MD5_DGS_SZ*2+1];
  
  nco_bool MD5_DGS_DSK=False; /* [flg] Perform MD5 digest of variable written to disk */

  nco_prg_id=nco_prg_id_get(); /* [enm] Program ID */

  /* MD5 digest of hyperslab already in RAM */
  (void)nco_md5_chk_ram(var_sz_byt,vp,md5_dgs_hxd_sng_ram);
  if(nco_prg_id == ncks && nco_dbg_lvl_get() >= nco_dbg_fl) (void)fprintf(stderr,"%s: INFO sizeof(%s MD5 buffer) = %ld B\n",nco_prg_nm_get(),var_nm,var_sz_byt);
  if((nco_prg_id == ncks && nco_dbg_lvl_get() >= nco_dbg_std) ||
     ((nco_prg_id == ncecat || nco_prg_id == ncrcat) && nco_dbg_lvl_get() >= nco_dbg_var) ||
     False)
    (void)fprintf(stderr,"%s: INFO MD5(%s) = %s\n",nco_prg_nm_get(),var_nm,md5_dgs_hxd_sng_ram);
  
  /* Write MD5 attributes */
  if(md5->wrt){
    /* Test with:
       ncks -O -C -4 -D 1 --md5_wrt -v md5_.? ~/nco/data/in.nc ~/foo.nc */
    aed_sct aed_md5;
    aed_md5.att_nm=md5->att_nm;
    aed_md5.var_nm=NULL;
    (void)nco_inq_varid(nc_id,var_nm,&aed_md5.id);
    aed_md5.sz=NCO_MD5_DGS_SZ*2L;
    aed_md5.type=NC_CHAR;
    aed_md5.val.cp=md5_dgs_hxd_sng_ram;
    aed_md5.mode=aed_overwrite;
    if(nco_dbg_lvl_get() >= nco_dbg_var) (void)fprintf(stderr,"%s: INFO Writing MD5 digest to attribute %s of variable %s\n",nco_prg_nm_get(),aed_md5.att_nm,var_nm);
    (void)nco_aed_prc(nc_id,aed_md5.id,aed_md5);
  } /* !wrt */

  /* NB: Setting this flag significantly increases execution time
     Comparing RAM to disk requires reading hyperslab from disk
     Hence it essentially doubles numbers of disk reads, e.g.,
     ncrcat reads/writes only one record of one variable at a time, and will perform an extra read to digest each write.
     Default strategy is to turn on MD5 disk-checking only when user is concatenating files */
  if(nco_prg_id == ncrcat || nco_prg_id == ncecat) MD5_DGS_DSK=True;  /* [flg] Perform MD5 digest of variable written to disk */

  /* Compare this digest to what is read in from output netCDF file
     This implementation re-uses vp to hold data read from disk 
     Advantages of this include lower overall memory usage
     Assumptions include:
     1. Original vp data are written to disk just before MD5 digest above
     2. Original vp data are no longer needed in calling routine, i.e., ncks or ncrcat
        Hence calls to nco_md5_chk() should occur just after put_vara() and prior to free()
     3. vp data are contiguous and may be read with a single get_vara() call */

  if(MD5_DGS_DSK){
    char md5_dgs_hxd_sng_dsk[NCO_MD5_DGS_SZ*2+1];

    int dmn_idx; 
    int dmn_nbr;
    int var_id;

    long var_sz_dsk=1L; /* [nbr] Size (in elements) of variable on disk */
    long var_sz_byt_dsk; /* [nbr] Size (in bytes) of variable (hyperslab) on disk */

    nc_type var_typ_dsk; /* [enm] netCDF type of variable on disk */

    /* Get var_id for requested variable */
    (void)nco_inq_varid(nc_id,var_nm,&var_id);
    
    /* Get type and number of dimensions for variable */
    (void)nco_inq_var(nc_id,var_id,(char *)NULL,&var_typ_dsk,&dmn_nbr,(int *)NULL,(int *)NULL);
    
    for(dmn_idx=0;dmn_idx<dmn_nbr;dmn_idx++) var_sz_dsk*=dmn_cnt[dmn_idx];

    var_sz_byt_dsk=var_sz_dsk*nco_typ_lng(var_typ_dsk);

    /* NB: If dmn_nbr is zero, then input dmn_srt and dmn_cnt are not used
       When the calling routine knows this, it may supply dmn_srt_and dmn_cnt as (const long * )NULL */
    if(dmn_nbr == 0) (void)nco_get_var1(nc_id,var_id,0L,vp,var_typ_dsk); else (void)nco_get_vara(nc_id,var_id,dmn_srt,dmn_cnt,vp,var_typ_dsk);

    (void)nco_md5_chk_ram(var_sz_byt_dsk,vp,md5_dgs_hxd_sng_dsk);
    if(strcmp(md5_dgs_hxd_sng_ram,md5_dgs_hxd_sng_dsk)){
      (void)fprintf(stderr,"%s: ERROR MD5(%s) RAM and disk disagree: %s != %s\n",nco_prg_nm_get(),var_nm,md5_dgs_hxd_sng_ram,md5_dgs_hxd_sng_dsk);
      nco_exit(EXIT_FAILURE);
    }else{ /* endif digests differ */

      if(nco_dbg_lvl_get() >= nco_dbg_var) (void)fprintf(stderr,"%s: INFO MD5 digests of RAM and disk contents for %s agree\n",nco_prg_nm_get(),var_nm);
    }  /* endif digests agree */

  } /* !MD5_DGS_DSK */

} /* end nco_md5_chk() */

void
nco_md5_chk_ram /* [fnc] Perform MD5 digest on hyperslab in RAM */
(const long var_sz_byt, /* I [nbr] Size (in bytes) of hyperslab */
 const void * const vp, /* I [val] Values to digest */
 char md5_dgs_hxd_sng[NCO_MD5_DGS_SZ*2+1]) /* O [sng] MD5 digest */
{
  /* Purpose: Perform MD5 digest on hyperslab */
  int idx_dgs; 
  
  md5_state_t md5_stt;
  md5_byte_t md5_dgs_byt[NCO_MD5_DGS_SZ];
  
  /* Sequence of MD5 digest determined by LPD's implementation */
  md5_init(&md5_stt);
  md5_append(&md5_stt,(const md5_byte_t *)vp,var_sz_byt);
  md5_finish(&md5_stt,md5_dgs_byt);
  
  for(idx_dgs=0;idx_dgs<NCO_MD5_DGS_SZ;++idx_dgs) sprintf(md5_dgs_hxd_sng+idx_dgs*2,"%02x",md5_dgs_byt[idx_dgs]);
} /* end nco_md5_chk_ram() */

/* Begin md5.c by LPD: */

/*
  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
  
  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.
  
  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:
  
  1. The origin of this software must not be misrepresented; you must not
  claim that you wrote the original software. If you use this software
  in a product, an acknowledgment in the product documentation would be
  appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
  misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
  
  L. Peter Deutsch
  ghost@aladdin.com
*/
/* $Id$ */
/*
  Independent implementation of MD5 (RFC 1321).
  
  This code implements the MD5 Algorithm defined in RFC 1321, whose
  text is available at
  http://www.ietf.org/rfc/rfc1321.txt
  The code is derived from the text of the RFC, including the test suite
  (section A.5) but excluding the rest of Appendix A.  It does not include
  any code or documentation that is identified in the RFC as being
  copyrighted.
  
  The original and principal author of md5.c is L. Peter Deutsch
  <ghost@aladdin.com>.  Other authors are noted in the change history
  that follows (in reverse chronological order):
  
  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
  either statically or dynamically; added missing #include <string.h>
  in library.
  2002-03-11 lpd Corrected argument list for main(), and added int return
  type, in test program and T value program.
  2002-02-21 lpd Added missing #include <stdio.h> in test program.
  2000-07-03 lpd Patched to eliminate warnings about "constant is
  unsigned in ANSI C, signed in traditional"; made test program
  self-checking.
  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
  1999-05-03 lpd Original version.
*/

#undef BYTE_ORDER	/* 1 = big-endian, -1 = little-endian, 0 = unknown */
#ifdef ARCH_IS_BIG_ENDIAN
#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
#else
#  define BYTE_ORDER 0
#endif

#define T_MASK ((md5_word_t)~0)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3    0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6    0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9    0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13    0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16    0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19    0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22    0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25    0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28    0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31    0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35    0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38    0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41    0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44    0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47    0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50    0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53    0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57    0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60    0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63    0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)

static void
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
{
  md5_word_t
    a = pms->abcd[0], b = pms->abcd[1],
    c = pms->abcd[2], d = pms->abcd[3];
  md5_word_t t;
#if BYTE_ORDER > 0
  /* Define storage only for big-endian CPUs. */
  md5_word_t X[16];
#else
  /* Define storage for little-endian or both types of CPUs. */
  md5_word_t xbuf[16];
  const md5_word_t *X;
#endif
  
  {
#if BYTE_ORDER == 0
    /*
     * Determine dynamically whether this is a big-endian or
     * little-endian machine, since we can use a more efficient
     * algorithm on the latter.
     */
    static const int w = 1;
    
    if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
#endif
#if BYTE_ORDER <= 0		/* little-endian */
      {
	/*
	 * On little-endian machines, we can process properly aligned
	 * data without copying it.
	 */
	if (!((data - (const md5_byte_t *)0) & 3)) {
	  /* data are properly aligned */
	  X = (const md5_word_t *)data;
	} else {
	  /* not aligned */
	  memcpy(xbuf, data, 64);
	  X = xbuf;
	}
      }
#endif
#if BYTE_ORDER == 0
    else			/* dynamic big-endian */
#endif
#if BYTE_ORDER >= 0		/* big-endian */
      {
	/*
	 * On big-endian machines, we must arrange the bytes in the
	 * right order.
	 */
	const md5_byte_t *xp = data;
	int i;
	
#  if BYTE_ORDER == 0
	X = xbuf;		/* (dynamic only) */
#  else
#    define xbuf X		/* (static only) */
#  endif
	for (i = 0; i < 16; ++i, xp += 4)
	  xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
      }
#endif
  }
  
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
  
  /* Round 1. */
  /* Let [abcd k s i] denote the operation
     a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)		\
  t = a + F(b,c,d) + X[k] + Ti;			\
  a = ROTATE_LEFT(t, s) + b
  /* Do the following 16 operations. */
  SET(a, b, c, d,  0,  7,  T1);
  SET(d, a, b, c,  1, 12,  T2);
  SET(c, d, a, b,  2, 17,  T3);
  SET(b, c, d, a,  3, 22,  T4);
  SET(a, b, c, d,  4,  7,  T5);
  SET(d, a, b, c,  5, 12,  T6);
  SET(c, d, a, b,  6, 17,  T7);
  SET(b, c, d, a,  7, 22,  T8);
  SET(a, b, c, d,  8,  7,  T9);
  SET(d, a, b, c,  9, 12, T10);
  SET(c, d, a, b, 10, 17, T11);
  SET(b, c, d, a, 11, 22, T12);
  SET(a, b, c, d, 12,  7, T13);
  SET(d, a, b, c, 13, 12, T14);
  SET(c, d, a, b, 14, 17, T15);
  SET(b, c, d, a, 15, 22, T16);
#undef SET
  
  /* Round 2. */
  /* Let [abcd k s i] denote the operation
     a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)		\
  t = a + G(b,c,d) + X[k] + Ti;			\
  a = ROTATE_LEFT(t, s) + b
  /* Do the following 16 operations. */
  SET(a, b, c, d,  1,  5, T17);
  SET(d, a, b, c,  6,  9, T18);
  SET(c, d, a, b, 11, 14, T19);
  SET(b, c, d, a,  0, 20, T20);
  SET(a, b, c, d,  5,  5, T21);
  SET(d, a, b, c, 10,  9, T22);
  SET(c, d, a, b, 15, 14, T23);
  SET(b, c, d, a,  4, 20, T24);
  SET(a, b, c, d,  9,  5, T25);
  SET(d, a, b, c, 14,  9, T26);
  SET(c, d, a, b,  3, 14, T27);
  SET(b, c, d, a,  8, 20, T28);
  SET(a, b, c, d, 13,  5, T29);
  SET(d, a, b, c,  2,  9, T30);
  SET(c, d, a, b,  7, 14, T31);
  SET(b, c, d, a, 12, 20, T32);
#undef SET
  
  /* Round 3. */
  /* Let [abcd k s t] denote the operation
     a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)		\
  t = a + H(b,c,d) + X[k] + Ti;			\
  a = ROTATE_LEFT(t, s) + b
  /* Do the following 16 operations. */
  SET(a, b, c, d,  5,  4, T33);
  SET(d, a, b, c,  8, 11, T34);
  SET(c, d, a, b, 11, 16, T35);
  SET(b, c, d, a, 14, 23, T36);
  SET(a, b, c, d,  1,  4, T37);
  SET(d, a, b, c,  4, 11, T38);
  SET(c, d, a, b,  7, 16, T39);
  SET(b, c, d, a, 10, 23, T40);
  SET(a, b, c, d, 13,  4, T41);
  SET(d, a, b, c,  0, 11, T42);
  SET(c, d, a, b,  3, 16, T43);
  SET(b, c, d, a,  6, 23, T44);
  SET(a, b, c, d,  9,  4, T45);
  SET(d, a, b, c, 12, 11, T46);
  SET(c, d, a, b, 15, 16, T47);
  SET(b, c, d, a,  2, 23, T48);
#undef SET
  
  /* Round 4. */
  /* Let [abcd k s t] denote the operation
     a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)		\
  t = a + I(b,c,d) + X[k] + Ti;			\
  a = ROTATE_LEFT(t, s) + b
  /* Do the following 16 operations. */
  SET(a, b, c, d,  0,  6, T49);
  SET(d, a, b, c,  7, 10, T50);
  SET(c, d, a, b, 14, 15, T51);
  SET(b, c, d, a,  5, 21, T52);
  SET(a, b, c, d, 12,  6, T53);
  SET(d, a, b, c,  3, 10, T54);
  SET(c, d, a, b, 10, 15, T55);
  SET(b, c, d, a,  1, 21, T56);
  SET(a, b, c, d,  8,  6, T57);
  SET(d, a, b, c, 15, 10, T58);
  SET(c, d, a, b,  6, 15, T59);
  SET(b, c, d, a, 13, 21, T60);
  SET(a, b, c, d,  4,  6, T61);
  SET(d, a, b, c, 11, 10, T62);
  SET(c, d, a, b,  2, 15, T63);
  SET(b, c, d, a,  9, 21, T64);
#undef SET
  
  /* Then perform the following additions. (That is increment each
     of the four registers by the value it had before this block
     was started.) */
  pms->abcd[0] += a;
  pms->abcd[1] += b;
  pms->abcd[2] += c;
  pms->abcd[3] += d;
}

void
md5_init(md5_state_t *pms)
{
  pms->count[0] = pms->count[1] = 0;
  pms->abcd[0] = 0x67452301;
  pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
  pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
  pms->abcd[3] = 0x10325476;
}

void
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
{
  const md5_byte_t *p = data;
  int left = nbytes;
  int offset = (pms->count[0] >> 3) & 63;
  md5_word_t nbits = (md5_word_t)(nbytes << 3);
  
  if (nbytes <= 0)
    return;
  
  /* Update the message length. */
  pms->count[1] += nbytes >> 29;
  pms->count[0] += nbits;
  if (pms->count[0] < nbits)
    pms->count[1]++;
  
  /* Process an initial partial block. */
  if (offset) {
    int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
    
    memcpy(pms->buf + offset, p, copy);
    if (offset + copy < 64)
      return;
    p += copy;
    left -= copy;
    md5_process(pms, pms->buf);
  }
  
  /* Process full blocks. */
  for (; left >= 64; p += 64, left -= 64)
    md5_process(pms, p);
  
  /* Process a final partial block. */
  if (left)
    memcpy(pms->buf, p, left);
}

void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
{
  static const md5_byte_t pad[64] = {
    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
  };
  md5_byte_t data[8];
  int i;
  
  /* Save the length before padding. */
  for (i = 0; i < 8; ++i)
    data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
  /* Pad to 56 bytes mod 64. */
  md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
  /* Append the length. */
  md5_append(pms, data, 8);
  for (i = 0; i < 16; ++i)
    digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}

