/*
 * fnv1a.c :  routines to create checksums derived from FNV-1a
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

#define APR_WANT_BYTEFUNC

#include <assert.h>
#include <apr.h>

#include "private/svn_subr_private.h"
#include "fnv1a.h"

/**
 * See http://www.isthe.com/chongo/tech/comp/fnv/ for more info on FNV-1
 */

/* FNV-1 32 bit constants taken from
 * http://www.isthe.com/chongo/tech/comp/fnv/
 */
#define FNV1_PRIME_32 0x01000193
#define FNV1_BASE_32 2166136261U

/* FNV-1a core implementation returning a 32 bit checksum over the first
 * LEN bytes in INPUT.  HASH is the checksum over preceding data (if any).
 */
static apr_uint32_t
fnv1a_32(apr_uint32_t hash, const void *input, apr_size_t len)
{
  const unsigned char *data = input;
  const unsigned char *end = data + len;

  for (; data != end; ++data)
    {
      hash ^= *data;
      hash *= FNV1_PRIME_32;
    }

  return hash;
}

/* Number of interleaved FVN-1a checksums we calculate for the modified
 * checksum algorithm.
 */
enum { SCALING = 4 };

/* FNV-1a core implementation updating 4 interleaved checksums in HASHES
 * over the first LEN bytes in INPUT.  This will only process multiples
 * of 4 and return the number of bytes processed.  LEN - ReturnValue < 4.
 */
static apr_size_t
fnv1a_32x4(apr_uint32_t hashes[SCALING], const void *input, apr_size_t len)
{
  /* calculate SCALING interleaved FNV-1a hashes while the input
     is large enough */
  const unsigned char *data = input;
  const unsigned char *end = data + len;
  for (; data + SCALING <= end; data += SCALING)
    {
      hashes[0] ^= data[0];
      hashes[0] *= FNV1_PRIME_32;
      hashes[1] ^= data[1];
      hashes[1] *= FNV1_PRIME_32;
      hashes[2] ^= data[2];
      hashes[2] *= FNV1_PRIME_32;
      hashes[3] ^= data[3];
      hashes[3] *= FNV1_PRIME_32;
    }

  return data - (const unsigned char *)input;
}

/* Combine interleaved HASHES plus LEN bytes from INPUT into a single
 * 32 bit hash value and return that.  LEN must be < 4.
 */
static apr_uint32_t
finalize_fnv1a_32x4(apr_uint32_t hashes[SCALING],
                    const void *input,
                    apr_size_t len)
{
  char final_data[sizeof(apr_uint32_t) * SCALING + SCALING - 1];
  apr_size_t i;
  assert(len < SCALING);

  for (i = 0; i < SCALING; ++i)
    hashes[i] = htonl(hashes[i]);

  /* run FNV-1a over the interleaved checksums plus the remaining
     (odd-lotted) input data */
  memcpy(final_data, hashes, sizeof(apr_uint32_t) * SCALING);
  if (len)
    memcpy(final_data + sizeof(apr_uint32_t) * SCALING, input, len);

  return fnv1a_32(FNV1_BASE_32,
                  final_data,
                  sizeof(apr_uint32_t) * SCALING + len);
}

apr_uint32_t
svn__fnv1a_32(const void *input, apr_size_t len)
{
  return fnv1a_32(FNV1_BASE_32, input, len);
}

apr_uint32_t
svn__fnv1a_32x4(const void *input, apr_size_t len)
{
  apr_uint32_t hashes[SCALING]
    = { FNV1_BASE_32, FNV1_BASE_32, FNV1_BASE_32, FNV1_BASE_32 };
  apr_size_t processed = fnv1a_32x4(hashes, input, len);

  return finalize_fnv1a_32x4(hashes,
                             (const char *)input + processed,
                             len - processed);
}

void
svn__fnv1a_32x4_raw(apr_uint32_t hashes[4],
                    const void *input,
                    apr_size_t len)
{
  apr_size_t processed;

  apr_size_t i;
  for (i = 0; i < SCALING; ++i)
    hashes[i] = FNV1_BASE_32;

  /* Process full 16 byte chunks. */
  processed = fnv1a_32x4(hashes, input, len);

  /* Fold the remainder (if any) into the first hash. */
  hashes[0] = fnv1a_32(hashes[0], (const char *)input + processed,
                       len - processed);
}

struct svn_fnv1a_32__context_t
{
  apr_uint32_t hash;
};

svn_fnv1a_32__context_t *
svn_fnv1a_32__context_create(apr_pool_t *pool)
{
  svn_fnv1a_32__context_t *context = apr_palloc(pool, sizeof(*context));
  context->hash = FNV1_BASE_32;

  return context;
}

void
svn_fnv1a_32__update(svn_fnv1a_32__context_t *context,
                     const void *data,
                     apr_size_t len)
{
  context->hash = fnv1a_32(context->hash, data, len);
}

apr_uint32_t
svn_fnv1a_32__finalize(svn_fnv1a_32__context_t *context)
{
  return context->hash;
}


struct svn_fnv1a_32x4__context_t
{
  apr_uint32_t hashes[SCALING];
  apr_size_t buffered;
  char buffer[SCALING];
};

svn_fnv1a_32x4__context_t *
svn_fnv1a_32x4__context_create(apr_pool_t *pool)
{
  svn_fnv1a_32x4__context_t *context = apr_palloc(pool, sizeof(*context));

  context->hashes[0] = FNV1_BASE_32;
  context->hashes[1] = FNV1_BASE_32;
  context->hashes[2] = FNV1_BASE_32;
  context->hashes[3] = FNV1_BASE_32;

  context->buffered = 0;

  return context;
}

void
svn_fnv1a_32x4__update(svn_fnv1a_32x4__context_t *context,
                       const void *data,
                       apr_size_t len)
{
  apr_size_t processed;

  if (context->buffered)
    {
      apr_size_t to_copy = SCALING - context->buffered;
      if (to_copy > len)
        {
          memcpy(context->buffer + context->buffered, data, len);
          context->buffered += len;
          return;
        }

      memcpy(context->buffer + context->buffered, data, to_copy);
      data = (const char *)data + to_copy;
      len -= to_copy;

      fnv1a_32x4(context->hashes, context->buffer, SCALING);
      context->buffered = 0;
    }

  processed = fnv1a_32x4(context->hashes, data, len);
  if (processed != len)
    {
      context->buffered = len - processed;
      memcpy(context->buffer,
             (const char*)data + processed,
             len - processed);
    }
}

apr_uint32_t
svn_fnv1a_32x4__finalize(svn_fnv1a_32x4__context_t *context)
{
  return finalize_fnv1a_32x4(context->hashes,
                             context->buffer,
                             context->buffered);
}
