/* See LICENSE file for copyright and license details. */
#include "common.h"
#ifdef LIBHASHSUM_INCLUDE_LIBKECCAK_STATE
LIBHASHSUM_1_NONNULL_
static void
stretch(struct libhashsum_hasher *this, int skip, void *buffer)
{
if (skip) {
libkeccak_fast_squeeze(&this->state.keccak.s, 1);
return;
}
if (buffer)
this->hash_output = NULL;
else if (this->hash_size > sizeof(this->state.keccak.sum.buf))
this->hash_output = this->state.keccak.sum.dyn;
else
this->hash_output = this->state.keccak.sum.buf;
libkeccak_squeeze(&this->state.keccak.s, buffer ? buffer : this->hash_output);
}
LIBHASHSUM_1_NONNULL_
static size_t
process(struct libhashsum_hasher *this, const void *data, size_t bytes)
{
bytes -= bytes % this->input_block_size;
libkeccak_zerocopy_update(&this->state.keccak.s, data, bytes);
return bytes;
}
LIBHASHSUM_1_NONNULL_
static void
finalise_common(struct libhashsum_hasher *this)
{
if (this->state.keccak.squeezes > 2U) {
size_t squeezes = this->state.keccak.squeezes - 2U;
while (squeezes > (size_t)LONG_MAX) {
libkeccak_fast_squeeze(&this->state.keccak.s, LONG_MAX);
squeezes -= (size_t)LONG_MAX;
}
libkeccak_fast_squeeze(&this->state.keccak.s, (long int)squeezes);
}
if (this->state.keccak.squeezes > 1U)
libkeccak_squeeze(&this->state.keccak.s, this->hash_output);
libkeccak_state_wipe_message(&this->state.keccak.s);
this->stretch = &stretch;
}
LIBHASHSUM_1_NONNULL_
static int
finalise_const(struct libhashsum_hasher *this, const void *data, size_t bytes, unsigned extra_bits)
{
const uint8_t *m = data;
size_t r;
if (extra_bits > 7U) {
errno = EINVAL;
return -1;
}
r = process(this, m, bytes);
m = &m[r];
bytes -= r;
if (!this->hash_output) {
if (this->hash_size > sizeof(this->state.keccak.sum.buf))
this->hash_output = this->state.keccak.sum.dyn;
else
this->hash_output = this->state.keccak.sum.buf;
}
libkeccak_digest(&this->state.keccak.s, m, bytes, extra_bits, this->state.keccak.suffix,
this->state.keccak.squeezes > 1U ? NULL : this->hash_output);
finalise_common(this);
return 0;
}
LIBHASHSUM_1_NONNULL_
static int
finalise(struct libhashsum_hasher *this, void *data, size_t bytes, unsigned extra_bits, size_t size)
{
uint8_t *m = data;
size_t r;
size_t need;
if (extra_bits > 7U) {
errno = EINVAL;
return -1;
}
r = process(this, m, bytes);
m = &m[r];
bytes -= r;
size -= r;
need = strlen(this->state.keccak.suffix) + 2U + extra_bits;
need = (need + 7U) >> 3;
need += bytes;
if (need & (this->input_block_size - 1U)) {
need &= ~(this->input_block_size - 1U);
need += this->input_block_size;
}
if (!this->hash_output) {
if (this->hash_size > sizeof(this->state.keccak.sum.buf))
this->hash_output = this->state.keccak.sum.dyn;
else
this->hash_output = this->state.keccak.sum.buf;
}
if (size < need)
libkeccak_digest(&this->state.keccak.s, m, bytes, extra_bits, this->state.keccak.suffix,
this->state.keccak.squeezes > 1U ? NULL : this->hash_output);
else
libkeccak_zerocopy_digest(&this->state.keccak.s, m, bytes, extra_bits, this->state.keccak.suffix,
this->state.keccak.squeezes > 1U ? NULL : this->hash_output);
finalise_common(this);
return 0;
}
LIBHASHSUM_1_NONNULL_
static void
destroy(struct libhashsum_hasher *this)
{
libkeccak_state_wipe_message(&this->state.keccak.s);
libkeccak_state_fast_destroy(&this->state.keccak.s);
memset(&this->state.keccak.s, 0, sizeof(this->state.keccak.s));
this->state.keccak.s.M = NULL;
if (this->hash_size > sizeof(this->state.keccak.sum.buf)) {
free(this->state.keccak.sum.dyn);
this->state.keccak.sum.dyn = NULL;
}
}
int
libhashsum_init_keccak__(struct libhashsum_hasher *this, size_t hashbits, void *spec_, size_t squeezes, const char *suffix)
{
struct libkeccak_spec *spec = spec_;
this->hash_size = hashbits >> 3;
this->hash_size += (size_t)!!(hashbits & 7U);
this->hash_output = NULL;
this->supports_non_whole_bytes = 1;
this->standard_partial_byte_input_encoding = LIBHASHSUM_LEAST_SIGNIFICANT;
this->standard_partial_byte_output_encoding = LIBHASHSUM_LEAST_SIGNIFICANT;
this->state.keccak.squeezes = squeezes;
this->state.keccak.suffix = suffix;
if (this->hash_size > sizeof(this->state.keccak.sum.buf)) {
this->state.keccak.sum.dyn = malloc(this->hash_size);
if (!this->state.keccak.sum.dyn)
return -1;
}
if (libkeccak_state_initialise(&this->state.keccak.s, spec)) {
if (this->hash_size > sizeof(this->state.keccak.sum.buf)) {
free(this->state.keccak.sum.dyn);
this->state.keccak.sum.dyn = NULL;
}
return -1;
}
this->input_block_size = libkeccak_zerocopy_chunksize(&this->state.keccak.s);
this->process = &process;
this->finalise_const = &finalise_const;
this->finalise = &finalise;
this->stretch = NULL;
this->destroy = &destroy;
return 0;
}
#else
int
libhashsum_init_keccak__(struct libhashsum_hasher *this, size_t hashbits, void *spec, size_t squeezes, const char *suffix);
{
(void) this;
(void) spec;
(void) squeezes;
(void) suffix;
errno = ENOSYS;
return -1;
}
#endif