/*
 * librdkafka - Apache Kafka C library
 *
 * Copyright (c) 2012-2022, Magnus Edenhill
 * 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.
 */

#include "rd.h"
#include "rdunittest.h"
#include "rdfnv1a.h"


/* FNV-1a by Glenn Fowler, Landon Curt Noll, and Kiem-Phong Vo
 *
 * Based on http://www.isthe.com/chongo/src/fnv/hash_32a.c
 * with librdkafka modifications to match the Sarama default Producer
 * implementation, as seen here:
 * https://github.com/Shopify/sarama/blob/master/partitioner.go#L203 Note that
 * this implementation is only compatible with Sarama's default
 * NewHashPartitioner and not NewReferenceHashPartitioner.
 */
uint32_t rd_fnv1a(const void *key, size_t len) {
        const uint32_t prime  = 0x01000193;  // 16777619
        const uint32_t offset = 0x811C9DC5;  // 2166136261
        size_t i;
        int32_t h = offset;

        const unsigned char *data = (const unsigned char *)key;

        for (i = 0; i < len; i++) {
                h ^= data[i];
                h *= prime;
        }

        /* Take absolute value to match the Sarama NewHashPartitioner
         * implementation */
        if (h < 0) {
                h = -h;
        }

        return (uint32_t)h;
}


/**
 * @brief Unittest for rd_fnv1a()
 */
int unittest_fnv1a(void) {
        const char *short_unaligned = "1234";
        const char *unaligned       = "PreAmbleWillBeRemoved,ThePrePartThatIs";
        const char *keysToTest[]    = {
            "kafka",
            "giberish123456789",
            short_unaligned,
            short_unaligned + 1,
            short_unaligned + 2,
            short_unaligned + 3,
            unaligned,
            unaligned + 1,
            unaligned + 2,
            unaligned + 3,
            "",
            NULL,
        };

        // Acquired via https://play.golang.org/p/vWIhw3zJINA
        const int32_t golang_hashfnv_results[] = {
            0xd33c4e1,   // kafka
            0x77a58295,  // giberish123456789
            0x23bdd03,   // short_unaligned
            0x2dea3cd2,  // short_unaligned+1
            0x740fa83e,  // short_unaligned+2
            0x310ca263,  // short_unaligned+3
            0x65cbd69c,  // unaligned
            0x6e49c79a,  // unaligned+1
            0x69eed356,  // unaligned+2
            0x6abcc023,  // unaligned+3
            0x7ee3623b,  // ""
            0x7ee3623b,  // NULL
        };

        size_t i;
        for (i = 0; i < RD_ARRAYSIZE(keysToTest); i++) {
                uint32_t h = rd_fnv1a(
                    keysToTest[i], keysToTest[i] ? strlen(keysToTest[i]) : 0);
                RD_UT_ASSERT((int32_t)h == golang_hashfnv_results[i],
                             "Calculated FNV-1a hash 0x%x for \"%s\", "
                             "expected 0x%x",
                             h, keysToTest[i], golang_hashfnv_results[i]);
        }
        RD_UT_PASS();
}
