/*
 * Copyright (C) 2008-2019 Tobias Brunner
 * Copyright (C) 2023 Andreas Steffen
 * Copyright (C) 2005-2008 Martin Willi
 * Copyright (C) 2005 Jan Hutter
 *
 * Copyright (C) secunet Security Networks AG
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

/**
 * @defgroup chunk chunk
 * @{ @ingroup utils
 */

#ifndef CHUNK_H_
#define CHUNK_H_

#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif

#include <utils/utils.h>

typedef struct chunk_t chunk_t;

/**
 * General purpose pointer/length abstraction.
 */
struct chunk_t {
	/** Pointer to start of data */
	u_char *ptr;
	/** Length of data in bytes */
	size_t len;
};

#include "utils.h"

/**
 * A { NULL, 0 }-chunk handy for initialization.
 */
extern chunk_t chunk_empty;

/**
 * Create a new chunk pointing to "ptr" with length "len"
 */
static inline chunk_t chunk_create(u_char *ptr, size_t len)
{
	chunk_t chunk = {ptr, len};
	return chunk;
}

/**
 * Create a clone of a chunk pointing to "ptr"
 */
chunk_t chunk_create_clone(u_char *ptr, chunk_t chunk);

/**
 * Calculate length of multiple chunks
 */
size_t chunk_length(const char *mode, ...);

/**
 * Concatenate chunks into a chunk pointing to "ptr".
 *
 * The mode string specifies the number of chunks, and how to handle each of
 * them with a single character: 'c' for copy (allocate new chunk), 'm' for move
 * (free given chunk) or 's' for sensitive-move (clear given chunk, then free).
 */
chunk_t chunk_create_cat(u_char *ptr, const char* mode, ...);

/**
 * Split up a chunk into parts, "mode" is a string of "a" (alloc),
 * "c" (copy) and "m" (move). Each letter say for the corresponding chunk if
 * it should get allocated on heap, copied into existing chunk, or the chunk
 * should point into "chunk". The length of each part is an argument before
 * each target chunk. E.g.:
 * chunk_split(chunk, "mcac", 3, &a, 7, &b, 5, &c, d.len, &d);
 */
void chunk_split(chunk_t chunk, const char *mode, ...);

/**
 * Write the binary contents of a chunk_t to a file
 *
 * If the write fails, errno is set appropriately.
 *
 * @param chunk			contents to write to file
 * @param path			path where file is written to
 * @param mask			file mode creation mask
 * @param force			overwrite existing file by force
 * @return				TRUE if write operation was successful
 */
bool chunk_write(chunk_t chunk, char *path, mode_t mask, bool force);

/**
 * Store data read from FD into a chunk
 *
 * On error, errno is set appropriately.
 *
 * @param fd			file descriptor to read from
 * @param chunk			chunk receiving allocated buffer
 * @return				TRUE if successful, FALSE on failure
 */
bool chunk_from_fd(int fd, chunk_t *chunk);

/**
 * mmap() a file to a chunk.
 *
 * The returned chunk structure is allocated from heap, but it must be freed
 * through chunk_unmap(). A user may alter the chunk ptr or len, but must pass
 * the chunk pointer returned from chunk_map() to chunk_unmap() after use.
 *
 * On error, errno is set appropriately.
 *
 * @param path			path of file to map
 * @param wr			TRUE to sync writes to disk
 * @return				mapped chunk, NULL on error
 */
chunk_t *chunk_map(char *path, bool wr);

/**
 * munmap() a chunk previously mapped with chunk_map().
 *
 * When unmapping a writable map, the return value should be checked to
 * ensure changes landed on disk.
 *
 * @param chunk			pointer returned from chunk_map()
 * @return				TRUE if changes written back to file
 */
bool chunk_unmap(chunk_t *chunk);

/**
 * munmap() a chunk previously mapped with chunk_map() after clearing it.
 *
 * @note Writable maps (i.e. created with wr = TRUE) are NOT cleared.
 *
 * When unmapping a writable map, the return value should be checked to
 * ensure changes landed on disk.
 *
 * @param chunk         pointer returned from chunk_map()
 * @return              TRUE if changes written back to file
 */
bool chunk_unmap_clear(chunk_t *chunk);

/**
 * Convert a chunk of data to hex encoding.
 *
 * The resulting string is '\\0' terminated, but the chunk does not include
 * the '\\0'. If buf is supplied, it must hold at least (chunk.len * 2 + 1).
 *
 * @param chunk			data to convert to hex encoding
 * @param buf			buffer to write to, NULL to malloc
 * @param uppercase		TRUE to use uppercase letters
 * @return				chunk of encoded data
 */
chunk_t chunk_to_hex(chunk_t chunk, char *buf, bool uppercase);

/**
 * Convert a hex encoded in a binary chunk.
 *
 * If buf is supplied, it must hold at least (hex.len / 2) + (hex.len % 2)
 * bytes. It is filled by the right to give correct values for short inputs.
 *
 * @param hex			hex encoded input data
 * @param buf			buffer to write decoded data, NULL to malloc
 * @return				converted data
 */
chunk_t chunk_from_hex(chunk_t hex, char *buf);

/**
 * Convert a chunk of data to its base64 encoding.
 *
 * The resulting string is '\\0' terminated, but the chunk does not include
 * the '\\0'. If buf is supplied, it must hold at least (chunk.len * 4 / 3 + 1).
 *
 * @param chunk			data to convert
 * @param buf			buffer to write to, NULL to malloc
 * @return				chunk of encoded data
 */
chunk_t chunk_to_base64(chunk_t chunk, char *buf);

/**
 * Convert a base64 in a binary chunk.
 *
 * If buf is supplied, it must hold at least (base64.len / 4 * 3).
 *
 * @param base64		base64 encoded input data
 * @param buf			buffer to write decoded data, NULL to malloc
 * @return				converted data
 */
chunk_t chunk_from_base64(chunk_t base64, char *buf);

/**
 * Convert a chunk of data to its base32 encoding.
 *
 * The resulting string is '\\0' terminated, but the chunk does not include
 * the '\\0'. If buf is supplied, it must hold (chunk.len * 8 / 5 + 1) bytes.
 *
 * @param chunk			data to convert
 * @param buf			buffer to write to, NULL to malloc
 * @return				chunk of encoded data
 */
chunk_t chunk_to_base32(chunk_t chunk, char *buf);

/**
 * Convert a chunk of data to decimal encoding.
 *
 * The resulting string is '\\0' terminated, but the chunk does not include
 * the '\\0'. If buf is supplied, it must hold at least (chunk.len * 2.41 + 1).
 *
 * @param chunk         data to convert to decimal encoding
 * @param buf           buffer to write to, NULL to malloc
 * @return              chunk of encoded data
 */
chunk_t chunk_to_dec(chunk_t chunk, char *buf);

/**
 * Free contents of a chunk
 */
static inline void chunk_free(chunk_t *chunk)
{
	free(chunk->ptr);
	*chunk = chunk_empty;
}

/**
 * Overwrite the contents of a chunk and free it
 */
static inline void chunk_clear(chunk_t *chunk)
{
	if (chunk->ptr)
	{
		memwipe(chunk->ptr, chunk->len);
		chunk_free(chunk);
	}
}

/**
 * Initialize a chunk using a char array
 */
#define chunk_from_chars(...) ((chunk_t){(u_char[]){__VA_ARGS__}, sizeof((u_char[]){__VA_ARGS__})})

/**
 * Initialize a chunk to point to a thing
 */
#define chunk_from_thing(thing) chunk_create((u_char*)&(thing), sizeof(thing))

/**
 * Initialize a chunk from a string, not containing 0-terminator
 */
#define chunk_from_str(str) ({char *x = (str); chunk_create((u_char*)x, strlen(x));})

/**
 * Allocate a chunk on the heap
 */
#define chunk_alloc(bytes) ({size_t x = (bytes); chunk_create(x ? malloc(x) : NULL, x);})

/**
 * Allocate a chunk on the stack
 */
#define chunk_alloca(bytes) ({size_t x = (bytes); chunk_create(x ? alloca(x) : NULL, x);})

/**
 * Clone a chunk on heap
 */
#define chunk_clone(chunk) ({chunk_t x = (chunk); chunk_create_clone(x.len ? malloc(x.len) : NULL, x);})

/**
 * Clone a chunk on stack
 */
#define chunk_clonea(chunk) ({chunk_t x = (chunk); chunk_create_clone(x.len ? alloca(x.len) : NULL, x);})

/**
 * Concatenate chunks into a chunk on heap
 */
#define chunk_cat(mode, ...) chunk_create_cat(malloc(chunk_length(mode, __VA_ARGS__)), mode, __VA_ARGS__)

/**
 * Concatenate chunks into a chunk on stack
 */
#define chunk_cata(mode, ...) chunk_create_cat(alloca(chunk_length(mode, __VA_ARGS__)), mode, __VA_ARGS__)

/**
 * Skip n bytes in chunk (forward pointer, shorten length)
 */
static inline chunk_t chunk_skip(chunk_t chunk, size_t bytes)
{
	if (chunk.len > bytes)
	{
		chunk.ptr += bytes;
		chunk.len -= bytes;
		return chunk;
	}
	return chunk_empty;
}

/**
 * Skip any leading zero-valued bytes
 */
static inline chunk_t chunk_skip_zero(chunk_t chunk)
{
	while (chunk.len > 1 && *chunk.ptr == 0x00)
	{
		chunk.ptr++;
		chunk.len--;
	}
	return chunk;
}

/**
 * Copy the data from src to dst, left-padded with chr if dst is longer,
 * otherwise data is copied truncated on the left.
 *
 * @param dst			data is copied here
 * @param src			data is copied from here
 * @param chr			value to use for padding if necessary
 * @return				the destination chunk
 */
chunk_t chunk_copy_pad(chunk_t dst, chunk_t src, u_char chr);

/**
 * Compare two chunks, returns zero if they are equal or negative/positive if
 * a is smaller/greater than b.
 *
 * Note that if the chunks are not of equal length, their prefix is not
 * compared, only their lengths.
 */
int chunk_compare(chunk_t a, chunk_t b);

/**
 * Compare two chunks, returns zero if they are equal or negative/positive if
 * a is smaller/greater than b.
 *
 * Unlike chunk_compare(), this first compares the two chunks' prefix and only
 * then falls back to comparing their lengths (shorter is "lower") if the
 * prefix is equal.
 *
 * This can e.g. be used to compare nonces.
 */
int chunk_compare_prefix(chunk_t a, chunk_t b);

/**
 * Compare two chunks for equality,
 * NULL chunks are never equal.
 */
static inline bool chunk_equals(chunk_t a, chunk_t b)
{
	return a.ptr != NULL  && b.ptr != NULL &&
			a.len == b.len && memeq(a.ptr, b.ptr, a.len);
}

/**
 * Compare a secret chunk against some arbitrary input in constant time for
 * cryptographic purposes.
 *
 * If neither chunk is NULL, this function is always constant time and bound by
 * the secret's length.  If the length is known to be equal, the order of the
 * arguments doesn't matter.
 *
 * @param secret        expected/reference value (loop bound)
 * @param input         value to verify against secret
 * @return              TRUE if the chunks are equal
 */
static inline bool chunk_equals_const(chunk_t secret, chunk_t input)
{
	u_int diff = 0;
	size_t i;

	if (!secret.ptr || !input.ptr)
	{
		return FALSE;
	}
	diff |= constant_time_neq64(secret.len, input.len);
	for (i = 0; i < secret.len; i++)
	{
		/* the ternary here is OK as the input length is known to the attacker
		 * and the overall time is always bound by the secret's length */
		diff |= secret.ptr[i] ^ (i < input.len ? input.ptr[i]: 0);
	}
	return !diff;
}

/**
 * Compare two chunks (given as pointers) for equality (useful as callback),
 * NULL chunks are never equal.
 */
static inline bool chunk_equals_ptr(chunk_t *a, chunk_t *b)
{
	return a != NULL && b != NULL && chunk_equals(*a, *b);
}

/**
 * Increment a chunk, as it would represent a network order integer.
 *
 * @param chunk			chunk to increment
 * @return				TRUE if an overflow occurred
 */
bool chunk_increment(chunk_t chunk);

/**
 * Check if a chunk has printable characters only.
 *
 * If sane is given, chunk is cloned into sane and all non printable characters
 * get replaced by "replace".
 *
 * @param chunk			chunk to check for printability
 * @param sane			pointer where sane version is allocated, or NULL
 * @param replace		character to use for replacing unprintable characters
 * @return				TRUE if all characters in chunk are printable
 */
bool chunk_printable(chunk_t chunk, chunk_t *sane, char replace);

/**
 * Seed initial key for chunk_hash().
 *
 * This call should get invoked once during startup. This is usually done
 * by calling library_init(). Calling it multiple times is safe, it gets
 * executed just once.
 */
void chunk_hash_seed();

/**
 * Computes a 32 bit hash of the given chunk.
 *
 * @note The output of this function is randomized, that is, it will only
 * produce the same output for the same input when calling it from the same
 * process.  For a more predictable hash function use chunk_hash_static()
 * instead.
 *
 * @note This hash is only intended for hash tables not for cryptographic
 * purposes.
 *
 * @param chunk			data to hash
 * @return				hash value
 */
uint32_t chunk_hash(chunk_t chunk);

/**
 * Same as chunk_hash() but takes a pointer to a chunk. Can be used in
 * hashtables.
 *
 * @param chunk			pointer to chunk to hash
 * @return				hash value
 */
uint32_t chunk_hash_ptr(chunk_t *chunk);

/**
 * Incremental version of chunk_hash. Use this to hash two or more chunks.
 *
 * @param chunk			data to hash
 * @param hash			previous hash value
 * @return				hash value
 */
uint32_t chunk_hash_inc(chunk_t chunk, uint32_t hash);

/**
 * Computes a 32 bit hash of the given chunk.
 *
 * Compared to chunk_hash() this will always calculate the same output for the
 * same input.  Therefore, it should not be used for hash tables (to prevent
 * hash flooding).
 *
 * @note This hash is not intended for cryptographic purposes.
 *
 * @param chunk			data to hash
 * @return				hash value
 */
uint32_t chunk_hash_static(chunk_t chunk);

/**
 * Incremental version of chunk_hash_static(). Use this to hash two or more
 * chunks in a predictable way.
 *
 * @param chunk			data to hash
 * @param hash			previous hash value
 * @return				hash value
 */
uint32_t chunk_hash_static_inc(chunk_t chunk, uint32_t hash);

/**
 * Computes a quick MAC from the given chunk and key using SipHash.
 *
 * The key must have a length of 128-bit (16 bytes).
 *
 * @note While SipHash has strong features using it for cryptographic purposes
 * is not recommended (in particular because of the rather short output size).
 *
 * @param chunk			data to process
 * @param key			key to use
 * @return				MAC for given input and key
 */
uint64_t chunk_mac(chunk_t chunk, u_char *key);

/**
 * Calculate the Internet Checksum according to RFC 1071 for the given chunk.
 *
 * If the result is used with chunk_internet_checksum_inc() and the data length
 * is not a multiple of 16 bit the checksum bytes have to be swapped to
 * compensate the even/odd alignment.
 *
 * @param data			data to process
 * @return				checksum (one's complement, network order)
 */
uint16_t chunk_internet_checksum(chunk_t data);

/**
 * Extend the given Internet Checksum (one's complement, in network byte order)
 * with the given data.
 *
 * If data is not a multiple of 16 bits the checksum may have to be swapped to
 * compensate even/odd alignment (see chunk_internet_checksum()).
 *
 * @param data			data to process
 * @param checksum		previous checksum (one's complement, network order)
 * @return				checksum (one's complement, network order)
 */
uint16_t chunk_internet_checksum_inc(chunk_t data, uint16_t checksum);

/**
 * printf hook function for chunk_t.
 *
 * Arguments are:
 *	chunk_t *chunk
 * Use #-modifier to print a compact version
 * Use +-modifier to print a compact version without separator
 */
int chunk_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec,
					  const void *const *args);

#endif /** CHUNK_H_ @}*/
