/* 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.
 */

/*
 * SipHash (C reference implementation, APR-ized), originating from:
 *      https://131002.net/siphash/siphash24.c.
 */

#include "apr_siphash.h"

#define ROTL64(x, n) (((x) << (n)) | ((x) >> (64 - (n))))

#define U8TO64_LE(p) \
    (((apr_uint64_t)((p)[0])      ) | \
     ((apr_uint64_t)((p)[1]) <<  8) | \
     ((apr_uint64_t)((p)[2]) << 16) | \
     ((apr_uint64_t)((p)[3]) << 24) | \
     ((apr_uint64_t)((p)[4]) << 32) | \
     ((apr_uint64_t)((p)[5]) << 40) | \
     ((apr_uint64_t)((p)[6]) << 48) | \
     ((apr_uint64_t)((p)[7]) << 56))

#define U64TO8_LE(p, v) \
do { \
    (p)[0] = (unsigned char)((v)      ); \
    (p)[1] = (unsigned char)((v) >>  8); \
    (p)[2] = (unsigned char)((v) >> 16); \
    (p)[3] = (unsigned char)((v) >> 24); \
    (p)[4] = (unsigned char)((v) >> 32); \
    (p)[5] = (unsigned char)((v) >> 40); \
    (p)[6] = (unsigned char)((v) >> 48); \
    (p)[7] = (unsigned char)((v) >> 56); \
} while (0)

#define SIPROUND() \
do { \
    v0 += v1; v1=ROTL64(v1,13); v1 ^= v0; v0=ROTL64(v0,32); \
    v2 += v3; v3=ROTL64(v3,16); v3 ^= v2; \
    v0 += v3; v3=ROTL64(v3,21); v3 ^= v0; \
    v2 += v1; v1=ROTL64(v1,17); v1 ^= v2; v2=ROTL64(v2,32); \
} while(0)

#define SIPHASH(r, s, n, k) \
do { \
    const unsigned char *ptr, *end; \
    apr_uint64_t v0, v1, v2, v3, m; \
    apr_uint64_t k0, k1; \
    unsigned int rem; \
    \
    k0 = U8TO64_LE(k + 0); \
    k1 = U8TO64_LE(k + 8); \
    v3 = k1 ^ (apr_uint64_t)0x7465646279746573ULL; \
    v2 = k0 ^ (apr_uint64_t)0x6c7967656e657261ULL; \
    v1 = k1 ^ (apr_uint64_t)0x646f72616e646f6dULL; \
    v0 = k0 ^ (apr_uint64_t)0x736f6d6570736575ULL; \
    \
    rem = (unsigned int)(n & 0x7); \
    for (ptr = s, end = ptr + n - rem; ptr < end; ptr += 8) { \
        m = U8TO64_LE(ptr); \
        v3 ^= m; \
        cROUNDS \
        v0 ^= m; \
    } \
    m = (apr_uint64_t)(n & 0xff) << 56; \
    switch (rem) { \
        case 7: m |= (apr_uint64_t)ptr[6] << 48; \
        case 6: m |= (apr_uint64_t)ptr[5] << 40; \
        case 5: m |= (apr_uint64_t)ptr[4] << 32; \
        case 4: m |= (apr_uint64_t)ptr[3] << 24; \
        case 3: m |= (apr_uint64_t)ptr[2] << 16; \
        case 2: m |= (apr_uint64_t)ptr[1] << 8; \
        case 1: m |= (apr_uint64_t)ptr[0]; \
        case 0: break; \
    } \
    v3 ^= m; \
    cROUNDS \
    v0 ^= m; \
    \
    v2 ^= 0xff; \
    dROUNDS \
    \
    r = v0 ^ v1 ^ v2 ^ v3; \
} while (0)

APR_DECLARE(apr_uint64_t) apr_siphash(const void *src, apr_size_t len,
                              const unsigned char key[APR_SIPHASH_KSIZE],
                                      unsigned int c, unsigned int d)
{
    apr_uint64_t h;
    unsigned int i;

#undef  cROUNDS
#define cROUNDS \
        for (i = 0; i < c; ++i) { \
            SIPROUND(); \
        }

#undef  dROUNDS
#define dROUNDS \
        for (i = 0; i < d; ++i) { \
            SIPROUND(); \
        }

    SIPHASH(h, src, len, key);
    return h;
}

APR_DECLARE(void) apr_siphash_auth(unsigned char out[APR_SIPHASH_DSIZE],
                                   const void *src, apr_size_t len,
                             const unsigned char key[APR_SIPHASH_KSIZE],
                                   unsigned int c, unsigned int d)
{
    apr_uint64_t h;
    h = apr_siphash(src, len, key, c, d);
    U64TO8_LE(out, h);
}

APR_DECLARE(apr_uint64_t) apr_siphash24(const void *src, apr_size_t len,
                               const unsigned char key[APR_SIPHASH_KSIZE])
{
    apr_uint64_t h;

#undef  cROUNDS
#define cROUNDS \
        SIPROUND(); \
        SIPROUND();

#undef  dROUNDS
#define dROUNDS \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND();

    SIPHASH(h, src, len, key);
    return h;
}

APR_DECLARE(void) apr_siphash24_auth(unsigned char out[APR_SIPHASH_DSIZE],
                                     const void *src, apr_size_t len,
                               const unsigned char key[APR_SIPHASH_KSIZE])
{
    apr_uint64_t h;
    h = apr_siphash24(src, len, key);
    U64TO8_LE(out, h);
}

APR_DECLARE(apr_uint64_t) apr_siphash48(const void *src, apr_size_t len,
                               const unsigned char key[APR_SIPHASH_KSIZE])
{
    apr_uint64_t h;

#undef  cROUNDS
#define cROUNDS \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND();

#undef  dROUNDS
#define dROUNDS \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND(); \
        SIPROUND();

    SIPHASH(h, src, len, key);
    return h;
}

APR_DECLARE(void) apr_siphash48_auth(unsigned char out[APR_SIPHASH_DSIZE],
                                     const void *src, apr_size_t len,
                               const unsigned char key[APR_SIPHASH_KSIZE])
{
    apr_uint64_t h;
    h = apr_siphash48(src, len, key);
    U64TO8_LE(out, h);
}

