text: /*  Part of SWI-Prolog
  
      Author:        Jan Wielemaker
      E-mail:        J.Wielemaker@vu.nl
      WWW:           http://www.swi-prolog.org
      Copyright (c)  2010-2015, University of Amsterdam
                                VU University Amsterdam
      All rights reserved.
  
      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions
      are met:
  
      1. Redistributions of source code must retain the above copyright
         notice, this list of conditions and the following disclaimer.
  
      2. Redistributions in binary form must reproduce the above copyright
         notice, this list of conditions and the following disclaimer in
         the documentation and/or other materials provided with the
         distribution.
  
      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      POSSIBILITY OF SUCH DAMAGE.
  */
  
  /*#define O_DEBUG 1*/
  #include "pl-incl.h"
  #include "pl-arith.h"
  #define AC_TERM_WALK 1
  #include "pl-termwalk.c"
  
  #ifdef __WINDOWS__
  typedef unsigned int uint32_t;
  #endif
  
  
  		 /*******************************
  		 *	    TERM_HASH/2		*
  		 *******************************/
  
  #define CYCLE_CONST 123456
  #define nodeID(ptr, b) ((ptr)-baseBuffer(b, th_data))
  #define nodePTR(id, b) (baseBuffer(b, th_data) + (id))
  
  
  typedef struct th_data
  { size_t	parent_offset;		/* My parent */
    Functor	term;			/* Term being processed */
    functor_t	functor;		/* Functor of the term */
    unsigned int	hash;			/* Hash collected sofar */
    unsigned	arg : 31;		/* Current argument */
    unsigned	in_cycle : 1;		/* We are part of a cycle */
  } th_data;
  
  
  static void *
  allocBuffer(Buffer b, size_t size)
  { void *ptr;
  
    if ( b->top + size > b->max )
    { if ( !growBuffer(b, size) )
        return NULL;
    }
    ptr = b->top;
    b->top += size;
  
    return ptr;
  }
  
  
  static int
  primitiveHashValue(word term, unsigned int *hval ARG_LD)
  { switch(tag(term))
    { case TAG_VAR:
      case TAG_ATTVAR:
        return FALSE;
      case TAG_ATOM:
      { *hval = MurmurHashAligned2(&atomValue(term)->hash_value,
  				 sizeof(unsigned int), *hval);
        return TRUE;
      }
      case TAG_STRING:
      { size_t len;
        char *s;
  
        s = getCharsString(term, &len);
        *hval = MurmurHashAligned2(s, len, *hval);
  
        return TRUE;
      }
      case TAG_INTEGER:
        if ( storage(term) == STG_INLINE )
        { int64_t v = valInt(term);
  
  	*hval = MurmurHashAligned2(&v, sizeof(v), *hval);
  
  	return TRUE;
        }
      /*FALLTHROUGH*/
      case TAG_FLOAT:
        { Word p = addressIndirect(term);
  	size_t n = wsizeofInd(*p);
  
  	*hval = MurmurHashAligned2(p+1, n*sizeof(word), *hval);
  
  	return TRUE;
        }
      default:
        assert(0);
        return FALSE;
    }
  }
  
  
  static void
  start_term(th_data *work, Buffer b, word w ARG_LD)
  { atom_t name;
  
    work->term     = valueTerm(w);
    work->functor  = work->term->definition;
    work->hash     = MURMUR_SEED;
    work->arg      = 0;
    work->in_cycle = 0;
  
    name = nameFunctor(work->functor);
    work->hash = MurmurHashAligned2(&atomValue(name)->hash_value,
  				  sizeof(unsigned int), work->hash);
  
    DEBUG(1, Sdprintf("Added node %ld, %s/%d, hash=%d\n",
  		    nodeID(work, b),
  		    stringAtom(name), arityFunctor(work->functor),
  		    work->hash));
  }
  
  
  /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  (*) This deals with A = s(A), hash_term(s(s(A)), Hash)
  This is not enough.  Consider:
  
  	Term = [X=s(Y),Y=Y], %where
  	X = s(Y),
  	Y = [X|X].
  
  Making every parent a cycle works, but might loose a bit too much :-(
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  
  static Word
  next_arg(th_data **workp, Buffer b ARG_LD)
  { th_data *work = *workp;
  
    for(;;)
    { if ( work->arg < arityFunctor(work->functor) )
      { Word p = &work->term->arguments[work->arg];
  
        DEBUG(1, Sdprintf("-> arg %d from node %ld\n",
  			work->arg, nodeID(work, b)));
  
        deRef(p);
        return p;
      } else if ( work->parent_offset != (size_t)-1 )
      { th_data *parent = nodePTR(work->parent_offset, b);
        unsigned int myhash;
  
        myhash = work->in_cycle ? CYCLE_CONST : work->hash;
        parent->hash = MurmurHashAligned2(&myhash, sizeof(myhash),
  					parent->hash);
        if ( work->in_cycle /*&& parent->functor == work->functor*/ ) /* (*) */
        { DEBUG(1, Sdprintf("Mark parent %ld as cycle\n",
  			  nodeID(parent, b)));
  	parent->in_cycle = TRUE;
        }
        DEBUG(1, Sdprintf("Updated hash in node %ld%s to %d\n",
  			work->parent_offset,
  			work->in_cycle ? " (cycle)" : "",
  			parent->hash));
  
        work = *workp = parent;
        work->arg++;
      } else
      { return NULL;
      }
    }
  }
  
  
  static void
  update_cycle(th_data *here, th_data *start, Buffer b)
  { unsigned int myhash = CYCLE_CONST;
  
    here->hash = MurmurHashAligned2(&myhash, sizeof(myhash),
  				  here->hash);
    DEBUG(1, Sdprintf("here = %ld, hash -> %d\n",
  		    nodeID(here, b), here->hash));
  
    for(;;)
    { DEBUG(1, Sdprintf("Marking cycle for node %ld\n",
  		      nodeID(here, b)));
      here->in_cycle = TRUE;
      if ( here == start )
        break;
      here = nodePTR(here->parent_offset, b);
    }
  }
  
  
  static int
  termHashValue(Word p, unsigned int *hval ARG_LD)
  { deRef(p);
    if ( !isTerm(*p) )
    { *hval = MURMUR_SEED;
      return primitiveHashValue(*p, hval PASS_LD);
    } else
    { tmp_buffer tmp;
      Buffer b = (Buffer)&tmp;
      th_data *work;
      int rc = TRUE;
      Functor t = valueTerm(*p);
  
      initBuffer(&tmp);
      work = allocBuffer(b, sizeof(*work));	/* cannot fail */
      start_term(work, b, *p PASS_LD);
      work->parent_offset = (size_t)-1;
      t->definition = consInt(0);
  
      while ( (p = next_arg(&work, b PASS_LD)) )
      { if ( !isTerm(*p) )
        { if ( primitiveHashValue(*p, &work->hash PASS_LD) )
  	{ work->arg++;
  	} else
  	{ rc = FALSE;
  	  goto out;
  	}
        } else
        { Functor t = valueTerm(*p);
  
  	if ( isInteger(t->definition) )
  	{ th_data *seen = nodePTR(valInt(t->definition), b);
  
  	  if ( seen->arg < arityFunctor(seen->functor) )
  	  { DEBUG(1, Sdprintf("cycle to %ld\n", valInt(t->definition)));
  	    update_cycle(work, seen, b);
  	  } else
  	  { unsigned int shash = seen->in_cycle ? CYCLE_CONST : seen->hash;
  
  	    work->hash = MurmurHashAligned2(&shash, sizeof(shash), work->hash);
  	    DEBUG(1, Sdprintf("shared with %ld, reusing hash %d node %ld -> %d\n",
  			      valInt(t->definition), seen->hash,
  			      nodeID(work, b), work->hash));
  	    if ( seen->in_cycle /*&& seen->functor == work->functor*/ )
  	      work->in_cycle = TRUE;
  	  }
  	  work->arg++;
  	} else
  	{ size_t parent = nodeID(work, b);
  
  	  if ( !(work = allocBuffer(b, sizeof(*work))) )
  	  { rc = -1;			/* out of memory */
  	    goto out;
  	  }
  	  start_term(work, b, *p PASS_LD);
  	  work->parent_offset = parent;
  	  t->definition = consInt(nodeID(work, b));
  	}
        }
      }
  					/* restore */
    out:;
      { th_data *d   = baseBuffer(b, th_data);
        th_data *end = d + entriesBuffer(b, th_data);
  
        if ( rc == TRUE )
  	*hval = d->hash;
  
        for(; d<end; d++)
        { d->term->definition = d->functor;
        }
      }
  
      discardBuffer(b);
  
      if ( rc < 0 )
        rc = PL_error(NULL, 0, NULL, ERR_NOMEM);
  
      return rc;
    }
  }
  
  
  /* term_hash(+Term, -HashKey) */
  
  static
  PRED_IMPL("term_hash", 2, term_hash, 0)
  { GET_LD
    Word p = valTermRef(A1);
    unsigned int hraw;
    int rc;
  
    rc = termHashValue(p, &hraw PASS_LD);
  
    if ( rc )
    { hraw = hraw & PLMAXTAGGEDINT32;	/* ensure tagged (portable) */
  
      return PL_unify_integer(A2, hraw);
    }
  
    return TRUE;
  }
  
  
  		 /*******************************
  		 *	    VARIANT SHA1	*
  		 *******************************/
  
  /* type to hold the SHA256 context  */
  
  #define SHA1_DIGEST_SIZE 20
  
  typedef struct
  {   uint32_t count[2];
      uint32_t hash[5];
      uint32_t wbuf[16];
  } sha1_ctx;
  
  /* Note that these prototypes are the same for both bit and */
  /* byte oriented implementations. However the length fields */
  /* are in bytes or bits as appropriate for the version used */
  /* and bit sequences are input as arrays of bytes in which  */
  /* bit sequences run from the most to the least significant */
  /* end of each byte                                         */
  
  #define VOID_RETURN static void
  
  VOID_RETURN sha1_compile(sha1_ctx ctx[1]);
  
  VOID_RETURN sha1_begin(sha1_ctx ctx[1]);
  VOID_RETURN sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1]);
  VOID_RETURN sha1_end(unsigned char hval[], sha1_ctx ctx[1]);
  
  
  		 /*******************************
  		 *    INCREMENTAL MURMUR HASH	*
  		 *******************************/
  
  #define HASH_BLOCK_SIZE 256
  
  typedef struct hash_state
  { unsigned int	  hash;
    size_t	  len;
    unsigned char	  buf[HASH_BLOCK_SIZE];
  } hash_state;
  
  static void
  hash_init(hash_state *state)
  { state->len  = 0;
    state->hash = MURMUR_SEED;
  }
  
  static void
  hash_compile(hash_state *state, const unsigned char *data, size_t len)
  { if ( len+state->len <= HASH_BLOCK_SIZE )
    { memcpy(&state->buf[state->len], data, len);
      state->len += len;
    } else
    { while(len > 0)
      { size_t copy = len;
        if ( len > HASH_BLOCK_SIZE-state->len )
  	copy = HASH_BLOCK_SIZE-state->len;
        len -= copy;
        memcpy(&state->buf[state->len], data, copy);
        state->len += copy;
        if ( state->len == HASH_BLOCK_SIZE )
        { state->hash = MurmurHashAligned2(state->buf, HASH_BLOCK_SIZE,
  					 state->hash);
  	state->len = 0;
        }
      }
    }
  }
  
  static unsigned int
  hash_end(hash_state *state)
  { return MurmurHashAligned2(state->buf, state->len, state->hash);
  }
  
  /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  State for processing the term
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  
  typedef enum
  { HASH_SHA1,
    HASH_MURMUR
  } hash_algo;
  
  typedef struct
  { int		var_count;
    hash_algo	algorithm;
    union
    { sha1_ctx	sha1[1];			/* The SHA1 Context */
      hash_state	murmur[1];
    } ctx;
    segstack	vars;
    Word		vars_first_chunk[64];
  } sha1_state;
  
  typedef enum
  { E_OK,
    E_ATTVAR,
    E_RESOURCE,
    E_CYCLE
  } status;
  
  
  static int
  push_var(Word p, sha1_state *state)
  { return pushSegStack(&state->vars, p, Word);
  }
  
  static int
  push_attvar(Word p, sha1_state *state)
  { Word w = (Word)*p;
    return ( pushSegStack(&state->vars, p, Word) &&
  	   pushSegStack(&state->vars, w, Word) );
  }
  
  #define HASH(p,l) \
  	do \
  	{ if (state->algorithm == HASH_SHA1) \
  	    sha1_hash((const unsigned char*)(p), (l), state->ctx.sha1); \
  	  else \
  	    hash_compile(state->ctx.murmur, (const unsigned char*)(p), (l)); \
  	} while(0)
  
  static status
  variant_sha1(ac_term_agenda *agenda, sha1_state *state ARG_LD)
  { Word p;
  
    while( (p=ac_nextTermAgenda(agenda)) )
    { word w = *p;
  
      switch(tag(w))
      { const char *type;
  
        case TAG_ATTVAR:
        { if ( state->algorithm == HASH_SHA1 )
  	{ return E_ATTVAR;
  	} else
  	{ if ( storage(w) == STG_GLOBAL )
  	  { word i = state->var_count++;
  
  	    if ( !push_attvar(p, state) )
  	      return E_RESOURCE;
  	    *p = (i<<LMASK_BITS)|TAG_VAR|MARK_MASK;
  	  }
  	  HASH("V", 1);
  	  HASH(p, sizeof(word));
  	  continue;
  	}
        }
        case TAG_VAR:
        { if ( isVar(w) )
  	{ word i = state->var_count++;
  
  	  if ( !push_var(p, state) )
  	    return E_RESOURCE;
  	  *p = (i<<LMASK_BITS)|TAG_VAR|MARK_MASK;
  	}
          HASH("V", 1);
  	HASH(p, sizeof(word));
  	continue;
        }
        case TAG_ATOM:
        { Atom av = atomValue(w);
  	HASH("A", 1);
  	HASH(&av->length, sizeof(av->length));
  	HASH(av->name, (unsigned long)av->length);
  	HASH(av->type->name, (unsigned long)strlen(av->type->name));
  
  					/* TBD: Include type */
  	continue;
        }
        case TAG_INTEGER:
        { if ( !isIndirect(w) )
  	{ int64_t val = valInteger(w);
  
  	  HASH("i", 1);
  	  HASH(&val, sizeof(val));
  	  continue;
  	}
  	type = "I";
  	goto hash_indirect;
        }
        case TAG_STRING:
  	type = "S";
          goto hash_indirect;
        case TAG_FLOAT:
  	type = "F";
        hash_indirect:
        { Word d = addressIndirect(w);
  	size_t n = wsizeofInd(*d)*sizeof(word);
  
          assert(tag(w) != TAG_FLOAT || n == sizeof(double));
  	HASH(type, 1);
  	HASH(d+1, (unsigned long)n);
  	continue;
        }
        case TAG_COMPOUND:
        { functor_t f;
  
  	switch(ac_pushTermAgenda(agenda, w, &f))
  	{ case -1:			/* Resource error */
  	    return E_RESOURCE;
  	  case FALSE:			/* Cycle */
  	    return E_CYCLE;
  	  default:
  	  { FunctorDef fd = valueFunctor(f);
  	    int arity = arityFunctor(f);
  
  	    Atom fn = atomValue(fd->name);
  
  	    HASH("T", 1);
  	    HASH(&fn->length, sizeof(fn->length));
  	    HASH(fn->name, (unsigned long)fn->length);
  	    HASH(&arity, sizeof(arity));
  	  }
  	}
  	continue;
        }
      }
    }
  
    return E_OK;
  }
  
  
  static int
  variant_hash(term_t term, term_t hash, hash_algo algorithm ARG_LD)
  { int rc;
    ac_term_agenda agenda;
    sha1_state state;
    Word p;
    int n;
  
    state.var_count = 0;
    state.algorithm = algorithm;
    if ( algorithm == HASH_SHA1 )
      sha1_begin(state.ctx.sha1);
    else
      hash_init(state.ctx.murmur);
    ac_initTermAgenda(&agenda, valTermRef(term));
    initSegStack(&state.vars, sizeof(Word),
  	       sizeof(state.vars_first_chunk), state.vars_first_chunk);
    rc = variant_sha1(&agenda, &state PASS_LD);
    ac_clearTermAgenda(&agenda);
    while(popSegStack(&state.vars, &p, Word))
    { word w = (word)p;
      if ( unlikely(isAttVar(w)) )
      { popSegStack(&state.vars, &p, Word);
        *p = w;
      } else
      { setVar(*p);
      }
    }
  
    DEBUG(CHK_SECURE, checkData(valTermRef(term)));
  
    switch( rc )
    { case E_ATTVAR:
        return PL_error(NULL, 0, NULL,
  		      ERR_TYPE, ATOM_free_of_attvar, term);
      case E_CYCLE:
        return PL_error(NULL, 0, NULL,
  		      ERR_TYPE, ATOM_acyclic_term, term);
      case E_RESOURCE:
        return PL_error(NULL, 0, NULL,
  		      ERR_RESOURCE, ATOM_memory);
    }
  
    if ( state.algorithm == HASH_SHA1 )
    { unsigned char sha1[SHA1_DIGEST_SIZE];
      char hex[SHA1_DIGEST_SIZE*2];
      const char hexd[] = "0123456789abcdef";
      char *o;
      const unsigned char *i;
  
      sha1_end(sha1, state.ctx.sha1);
      o = hex;
      i = sha1;
      for(n=0; n<SHA1_DIGEST_SIZE; n++,i++)
      { *o++ = hexd[*i >> 4];
        *o++ = hexd[*i&0x0f];
      }
  
      return PL_unify_chars(hash, PL_ATOM|REP_ISO_LATIN_1, sizeof(hex), hex);
    } else
    { unsigned int key = hash_end(state.ctx.murmur)&PLMAXTAGGEDINT32;
  
      return PL_unify_integer(hash, key);
    }
  }
  
  /** variant_sha1(@Term, -SHA1:string) is det.
  
  Compute an SHA1 hash for Term. The hash  is designed such that two terms
  have the same hash iff variant(T1,T2) is true. This implies that we must
  basically execute numbervars.
  */
  
  static
  PRED_IMPL("variant_sha1", 2, variant_sha1, 0)
  { PRED_LD
    return variant_hash(A1, A2, HASH_SHA1 PASS_LD);
  }
  
  static
  PRED_IMPL("variant_hash", 2, variant_hash, 0)
  { PRED_LD
    return variant_hash(A1, A2, HASH_MURMUR PASS_LD);
  }
  
  		 /*******************************
  		 *      PUBLISH PREDICATES	*
  		 *******************************/
  
  BeginPredDefs(termhash)
    PRED_DEF("variant_sha1", 2, variant_sha1, 0)
    PRED_DEF("variant_hash", 2, variant_hash, 0)
    PRED_DEF("term_hash",    2, term_hash,    0)
  EndPredDefs
  
  		 /*******************************
  		 *	  INCLUDED STUFF	*
  		 *******************************/
  
  /*
   ---------------------------------------------------------------------------
   Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.
  
   LICENSE TERMS
  
   The free distribution and use of this software in both source and binary
   form is allowed (with or without changes) provided that:
  
     1. distributions of this source code include the above copyright
        notice, this list of conditions and the following disclaimer;
  
     2. distributions in binary form include the above copyright
        notice, this list of conditions and the following disclaimer
        in the documentation and/or other associated materials;
  
     3. the copyright holder's name is not used to endorse products
        built using this software without specific written permission.
  
   ALTERNATIVELY, provided that this notice is retained in full, this product
   may be distributed under the terms of the GNU General Public License (GPL),
   in which case the provisions of the GPL apply INSTEAD OF those given above.
  
   DISCLAIMER
  
   This software is provided 'as is' with no explicit or implied warranties
   in respect of its properties, including, but not limited to, correctness
   and/or fitness for purpose.
   ---------------------------------------------------------------------------
   Issue Date: 01/08/2005
  
   This is a byte oriented version of SHA1 that operates on arrays of bytes
   stored in memory.
  */
  
  #include <string.h>     /* for memcpy() etc.        */
  
  /*
   ---------------------------------------------------------------------------
   Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.
  
   LICENSE TERMS
  
   The free distribution and use of this software in both source and binary
   form is allowed (with or without changes) provided that:
  
     1. distributions of this source code include the above copyright
        notice, this list of conditions and the following disclaimer;
  
     2. distributions in binary form include the above copyright
        notice, this list of conditions and the following disclaimer
        in the documentation and/or other associated materials;
  
     3. the copyright holder's name is not used to endorse products
        built using this software without specific written permission.
  
   ALTERNATIVELY, provided that this notice is retained in full, this product
   may be distributed under the terms of the GNU General Public License (GPL),
   in which case the provisions of the GPL apply INSTEAD OF those given above.
  
   DISCLAIMER
  
   This software is provided 'as is' with no explicit or implied warranties
   in respect of its properties, including, but not limited to, correctness
   and/or fitness for purpose.
   ---------------------------------------------------------------------------
   Issue Date: 01/08/2005
  */
  
  #define SHA1_BLOCK_SIZE  64
  
  #define IS_BIG_ENDIAN      4321 /* byte 0 is most significant (mc68k) */
  #define IS_LITTLE_ENDIAN   1234 /* byte 0 is least significant (i386) */
  
  #if WORDS_BIGENDIAN
  #define PLATFORM_BYTE_ORDER IS_BIG_ENDIAN
  #else
  #define PLATFORM_BYTE_ORDER IS_LITTLE_ENDIAN
  #endif
  
  #define rotl32(x,n)   (((x) << n) | ((x) >> (32 - n)))
  #define rotr32(x,n)   (((x) >> n) | ((x) << (32 - n)))
  
  #if !defined(bswap_32)
  #define bswap_32(x) ((rotr32((x), 24) & 0x00ff00ff) | (rotr32((x), 8) & 0xff00ff00))
  #endif
  
  #if (PLATFORM_BYTE_ORDER == IS_LITTLE_ENDIAN)
  #define SWAP_BYTES
  #else
  #undef  SWAP_BYTES
  #endif
  
  #if defined(SWAP_BYTES)
  #define bsw_32(p,n) \
      { int _i = (n); while(_i--) ((uint32_t*)p)[_i] = bswap_32(((uint32_t*)p)[_i]); }
  #else
  #define bsw_32(p,n)
  #endif
  
  #define SHA1_MASK   (SHA1_BLOCK_SIZE - 1)
  
  #define ch(x,y,z)       ((z) ^ ((x) & ((y) ^ (z))))
  #define parity(x,y,z)   ((x) ^ (y) ^ (z))
  #define maj(x,y,z)      (((x) & (y)) | ((z) & ((x) ^ (y))))
  
  /* Compile 64 bytes of hash data into SHA1 context. Note    */
  /* that this routine assumes that the byte order in the     */
  /* ctx->wbuf[] at this point is in such an order that low   */
  /* address bytes in the ORIGINAL byte stream will go in     */
  /* this buffer to the high end of 32-bit words on BOTH big  */
  /* and little endian systems                                */
  
  #ifdef ARRAY
  #define q(v,n)  v[n]
  #else
  #define q(v,n)  v##n
  #endif
  
  #define one_cycle(v,a,b,c,d,e,f,k,h)            \
      q(v,e) += rotr32(q(v,a),27) +               \
                f(q(v,b),q(v,c),q(v,d)) + k + h;  \
      q(v,b)  = rotr32(q(v,b), 2)
  
  #define five_cycle(v,f,k,i)                 \
      one_cycle(v, 0,1,2,3,4, f,k,hf(i  ));   \
      one_cycle(v, 4,0,1,2,3, f,k,hf(i+1));   \
      one_cycle(v, 3,4,0,1,2, f,k,hf(i+2));   \
      one_cycle(v, 2,3,4,0,1, f,k,hf(i+3));   \
      one_cycle(v, 1,2,3,4,0, f,k,hf(i+4))
  
  VOID_RETURN sha1_compile(sha1_ctx ctx[1])
  {   uint32_t    *w = ctx->wbuf;
  
  #ifdef ARRAY
      uint32_t    v[5];
      memcpy(v, ctx->hash, 5 * sizeof(uint32_t));
  #else
      uint32_t    v0, v1, v2, v3, v4;
      v0 = ctx->hash[0]; v1 = ctx->hash[1];
      v2 = ctx->hash[2]; v3 = ctx->hash[3];
      v4 = ctx->hash[4];
  #endif
  
  #define hf(i)   w[i]
  
      five_cycle(v, ch, 0x5a827999,  0);
      five_cycle(v, ch, 0x5a827999,  5);
      five_cycle(v, ch, 0x5a827999, 10);
      one_cycle(v,0,1,2,3,4, ch, 0x5a827999, hf(15)); \
  
  #undef  hf
  #define hf(i) (w[(i) & 15] = rotl32(                    \
                   w[((i) + 13) & 15] ^ w[((i) + 8) & 15] \
                 ^ w[((i) +  2) & 15] ^ w[(i) & 15], 1))
  
      one_cycle(v,4,0,1,2,3, ch, 0x5a827999, hf(16));
      one_cycle(v,3,4,0,1,2, ch, 0x5a827999, hf(17));
      one_cycle(v,2,3,4,0,1, ch, 0x5a827999, hf(18));
      one_cycle(v,1,2,3,4,0, ch, 0x5a827999, hf(19));
  
      five_cycle(v, parity, 0x6ed9eba1,  20);
      five_cycle(v, parity, 0x6ed9eba1,  25);
      five_cycle(v, parity, 0x6ed9eba1,  30);
      five_cycle(v, parity, 0x6ed9eba1,  35);
  
      five_cycle(v, maj, 0x8f1bbcdc,  40);
      five_cycle(v, maj, 0x8f1bbcdc,  45);
      five_cycle(v, maj, 0x8f1bbcdc,  50);
      five_cycle(v, maj, 0x8f1bbcdc,  55);
  
      five_cycle(v, parity, 0xca62c1d6,  60);
      five_cycle(v, parity, 0xca62c1d6,  65);
      five_cycle(v, parity, 0xca62c1d6,  70);
      five_cycle(v, parity, 0xca62c1d6,  75);
  
  #ifdef ARRAY
      ctx->hash[0] += v[0]; ctx->hash[1] += v[1];
      ctx->hash[2] += v[2]; ctx->hash[3] += v[3];
      ctx->hash[4] += v[4];
  #else
      ctx->hash[0] += v0; ctx->hash[1] += v1;
      ctx->hash[2] += v2; ctx->hash[3] += v3;
      ctx->hash[4] += v4;
  #endif
  }
  
  VOID_RETURN sha1_begin(sha1_ctx ctx[1])
  {
      ctx->count[0] = ctx->count[1] = 0;
      ctx->hash[0] = 0x67452301;
      ctx->hash[1] = 0xefcdab89;
      ctx->hash[2] = 0x98badcfe;
      ctx->hash[3] = 0x10325476;
      ctx->hash[4] = 0xc3d2e1f0;
  }
  
  /* SHA1 hash data in an array of bytes into hash buffer and */
  /* call the hash_compile function as required.              */
  
  VOID_RETURN sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1])
  {   uint32_t pos = (uint32_t)(ctx->count[0] & SHA1_MASK),
              space = SHA1_BLOCK_SIZE - pos;
      const unsigned char *sp = data;
  
      if((ctx->count[0] += len) < len)
          ++(ctx->count[1]);
  
      while(len >= space)     /* tranfer whole blocks if possible  */
      {
          memcpy(((unsigned char*)ctx->wbuf) + pos, sp, space);
          sp += space; len -= space; space = SHA1_BLOCK_SIZE; pos = 0;
          bsw_32(ctx->wbuf, SHA1_BLOCK_SIZE >> 2);
          sha1_compile(ctx);
      }
  
      memcpy(((unsigned char*)ctx->wbuf) + pos, sp, len);
  }
  
  /* SHA1 final padding and digest calculation  */
  
  VOID_RETURN sha1_end(unsigned char hval[], sha1_ctx ctx[1])
  {   uint32_t    i = (uint32_t)(ctx->count[0] & SHA1_MASK);
  
      /* put bytes in the buffer in an order in which references to   */
      /* 32-bit words will put bytes with lower addresses into the    */
      /* top of 32 bit words on BOTH big and little endian machines   */
      bsw_32(ctx->wbuf, (i + 3) >> 2);
  
      /* we now need to mask valid bytes and add the padding which is */
      /* a single 1 bit and as many zero bits as necessary. Note that */
      /* we can always add the first padding byte here because the    */
      /* buffer always has at least one empty slot                    */
      ctx->wbuf[i >> 2] &= 0xffffff80U << 8 * (~i & 3);
      ctx->wbuf[i >> 2] |= 0x00000080U << 8 * (~i & 3);
  
      /* we need 9 or more empty positions, one for the padding byte  */
      /* (above) and eight for the length count. If there is not      */
      /* enough space, pad and empty the buffer                       */
      if(i > SHA1_BLOCK_SIZE - 9)
      {
          if(i < 60) ctx->wbuf[15] = 0;
          sha1_compile(ctx);
          i = 0;
      }
      else    /* compute a word index for the empty buffer positions  */
          i = (i >> 2) + 1;
  
      while(i < 14) /* and zero pad all but last two positions        */
          ctx->wbuf[i++] = 0;
  
      /* the following 32-bit length fields are assembled in the      */
      /* wrong byte order on little endian machines but this is       */
      /* corrected later since they are only ever used as 32-bit      */
      /* word values.                                                 */
      ctx->wbuf[14] = (ctx->count[1] << 3) | (ctx->count[0] >> 29);
      ctx->wbuf[15] = ctx->count[0] << 3;
      sha1_compile(ctx);
  
      /* extract the hash value as bytes in case the hash buffer is   */
      /* misaligned for 32-bit words                                  */
      for(i = 0; i < SHA1_DIGEST_SIZE; ++i)
          hval[i] = (unsigned char)(ctx->hash[i >> 2] >> (8 * (~i & 3)));
  }
  