/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2018 Danny Robson <danny@nerdcruft.net>
 */

#include "siphash.hpp"

#include "../bitwise.hpp"
#include "../debug.hpp"
#include "../endian.hpp"

using cruft::hash::siphash;


///////////////////////////////////////////////////////////////////////////////
static constexpr
uint64_t INITIALISERS[4] = {
    0x736f6d6570736575,
    0x646f72616e646f6d,
    0x6c7967656e657261,
    0x7465646279746573,
};


///////////////////////////////////////////////////////////////////////////////
static void
round (uint64_t v[4])
{
    using cruft::rotatel;

    v[0] += v[1];              v[2] += v[3];
    v[1] = rotatel (v[1], 13); v[3] = rotatel (v[3], 16);
    v[1] ^= v[0];              v[3] ^= v[2];
    v[0] = rotatel (v[0], 32);

    v[2] += v[1];               v[0] += v[3];
    v[1]  = rotatel (v[1], 17); v[3] = rotatel (v[3], 21);
    v[1] ^= v[2];               v[3] ^= v[0];
    v[2]  = rotatel (v[2], 32);
}


///////////////////////////////////////////////////////////////////////////////
template <int C, int D>
siphash<C,D>::siphash (std::array<uint64_t,2> _key) noexcept:
    m_key (_key)
{ ; }


//-----------------------------------------------------------------------------
template <int C, int D>
typename siphash<C,D>::digest_t
siphash<C,D>::operator() (cruft::view<const uint8_t*> data) const noexcept
{
    // init
    uint64_t state[4] = {
        m_key[0] ^ INITIALISERS[0],
        m_key[1] ^ INITIALISERS[1],
        m_key[0] ^ INITIALISERS[2],
        m_key[1] ^ INITIALISERS[3],
    };

    // update
    auto cursor = data.begin ();
    for ( ; data.end () - cursor > 8; cursor += sizeof (uint64_t)) {
        auto word = readle<uint64_t> (cursor);

        state[3] ^= word;
        for (int c = 0; c < C; ++c)
            round (state);
        state[0] ^= word;
    }

    // drain
    union {
        uint64_t d64 = 0;
        uint8_t d08[8];
    } accum;

    if (cursor != data.cend ()) {
        std::copy (cursor, data.cend (), std::begin (accum.d08));
        cursor = data.cend ();
    }

    CHECK_EQ (cursor, data.cend ());
    // append the length
    accum.d08[7] = data.size ();

    state[3] ^= accum.d64;
    for (int c = 0; c < C; ++c)
        round (state);
    state[0] ^= accum.d64;

    // finalisation
    state[2] ^= 0xff;
    for (int d = 0; d < D; ++d)
        round (state);

    return state[0] ^ state[1] ^ state[2] ^ state[3];
}


///////////////////////////////////////////////////////////////////////////////
template class cruft::hash::siphash<2,4>;