/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * 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.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * 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.
 */

#include <opencm.h>

/* #define BLKCHARS 1024 */
/* #define EAGER */
/* #define BUFCHECK */

#if !defined(BLKCHARS) && defined(BUFCHECK)
#error "Silly rabbit! You can't check buffering that you aren't doing!"
#endif
#if !defined(BLKCHARS) && defined(EAGER)
#error "Silly rabbit! You can't eagerly consume buffers you don't build!"
#endif

struct OPENCM_SHA {
  SHA_CTX context;

#ifdef BLKCHARS
  unsigned char *buf;		/* pending buffer */
  unsigned buflen;		/* length of current block */
#endif

  unsigned totlen;

  unsigned char hash[20];

#ifdef BUFCHECK
  SHA_CTX context2;
  unsigned totlen2;
  unsigned char hash2[20];
#endif

  OC_bool isFinished;
};

OPENCM_SHA *
sha_create()
{
  OPENCM_SHA *sha = (OPENCM_SHA *) GC_MALLOC(sizeof(OPENCM_SHA));
  
  SHA1_Init(&sha->context);

#ifdef BLKCHARS
  sha->buf = GC_MALLOC_ATOMIC(BLKCHARS);
  sha->buflen = 0;
#endif
  sha->totlen = 0;

#ifdef BUFCHECK
  SHA1_Init(&sha->context2);
  sha->totlen2 = 0;
#endif

  sha->isFinished = FALSE;

  return sha;
}

void
sha_append(OPENCM_SHA * sha, unsigned len, const void *buf)
{
#ifdef BLKCHARS
  const unsigned char *bp = (const unsigned char *) buf;
#endif

  if (sha->isFinished)
    return;

#ifdef BUFCHECK
  SHA1_Update(&sha->context2, buf, len);
  sha->totlen2 += len;
#endif

#ifdef BLKCHARS
  for (;len > 0;) {
    unsigned room = BLKCHARS - sha->buflen;
    unsigned take = min(len, room);

    memcpy(&sha->buf[sha->buflen], bp, take);

    sha->buflen += take;
    bp += take;
    len -= take;

    if (sha->buflen == BLKCHARS) {
      SHA1_Update(&sha->context, sha->buf, sha->buflen);
      sha->totlen += sha->buflen;

      sha->buflen = 0;
    }
  }
#else
  SHA1_Update(&sha->context, buf, len);
  sha->totlen += len;
#endif

#if 0
  if ((sha->totlen == sha->totlen2) && (sha->buflen == 0) &&
      (memcmp(&sha->context, &sha->context2, sizeof(sha->context)) != 0)) {
    log_trace(ERR_SHA1, "Append() SHA1 contexts don't agree at %d:\n", sha->totlen);
    log_trace(ERR_SHA1, "%s\n", hex_encode(&sha->context, sizeof(sha->context)));
    log_trace(ERR_SHA1, "%s\n", hex_encode(&sha->context2, sizeof(sha->context2)));
  }
#endif

#ifdef EAGER
  if (sha->buflen) {
    SHA1_Update(&sha->context, sha->buf, sha->buflen);
    sha->totlen += sha->buflen;
    sha->buflen = 0;
  }
#endif
}

void
sha_finish(OPENCM_SHA *sha)
{
#ifdef BUFCHECK
  OC_bool bad = FALSE;
#endif

  if (sha->isFinished)
    return;
  
#ifdef BLKCHARS
  assert(sha->buflen <= BLKCHARS);
#endif
#ifdef EAGER
  assert(sha->buflen == 0);
#endif

#ifdef BLKCHARS
  if (sha->buflen > 0) {
    SHA1_Update(&sha->context, sha->buf, sha->buflen);
    sha->totlen += sha->buflen;
  }
#endif

  SHA1_Final(sha->hash, &sha->context);

#ifdef BUFCHECK
  SHA1_Final(sha->hash2, &sha->context2);

  if (memcmp(&sha->context, &sha->context2, sizeof(sha->context)) != 0) {
    log_trace(ERR_SHA1, "SHA1 contexts don't agree:\n");
    log_trace(ERR_SHA1, "%s\n", hex_encode(&sha->context, sizeof(sha->context)));
    log_trace(ERR_SHA1, "%s\n", hex_encode(&sha->context2, sizeof(sha->context2)));
    bad = TRUE;
  }

  if (bad || sha->totlen2 == 1116116) {
    const char *s1 = hex_encode(sha->hash, 20);
    const char *s2 = hex_encode(sha->hash2, 20);
    log_trace(ERR_SHA1, "sha1-1: %s\n", s1);
    log_trace(ERR_SHA1, "sha1-2: %s\n", s2);

    if (!nmequal(s1, s2))
      log_trace(ERR_SHA1, "Residual was %d\n", sha->buflen);
  }

  if (memcmp(sha->hash, sha->hash2, 20) != 0) {
    log_trace(ERR_SHA1, "Raw sha hash mismatch! (%d, %d)\n", sha->totlen, sha->totlen2);

#if 0
    log_trace(ERR_SHA1, "Resid hashes: 0x%x 0x%x\n", sha->resid1_sxdhash, 
	    sxd_sum(sha->block, sha->blen));
#endif

    THROW(ExBadValue, "Bogus hash bogosity");
  }
#endif

  sha->isFinished = TRUE;
}

OPENCM_SHA *
sha_from_string(const char *s)
{
  OPENCM_SHA *sha = sha_create();
  sha_append(sha, strlen(s), s);
  sha_finish(sha);

#ifdef TEST_SHA
  {
    const char *s1;
    const char *s2;
    SHA_CTX context;
    char md[20];
    SHA1_Init(&context);
    SHA1_Update(&context, s, strlen(s));
    SHA1_Final(md, &context);

    s1 = sha_hexdigest(sha);
    s2 = b64_encode(md, 20);

    if (strcmp(s1, s2) != 0) {
      THROW(ExAssertFail,
            format("Hashes do not match: %s %s", s1, s2));
    }
  }
#endif

  return sha;
}

char *
sha_hexdigest(OPENCM_SHA *sha)
{
  assert(sha->isFinished);

#ifdef USE_BASE16
  return hex_encode(sha->hash, 20);
#else
  return b64_encode(sha->hash, 20);
#endif
}
