/*
 * Copyright (C) 2002-2012 Free Software Foundation, Inc.
 * Copyright (C) 2016-2017 Red Hat, Inc.
 *
 * Author: Nikos Mavrogiannopoulos
 *
 * This file is part of GnuTLS.
 *
 * The GnuTLS is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>
 *
 */

#include "gnutls_int.h"
#include "errors.h"
#include "num.h"
#include "str.h"
#include <stdarg.h>
#include <c-ctype.h>
#include <intprops.h>
#include <nettle/base64.h>
#include "extras/hex.h"

/* These functions are like strcat, strcpy. They only
 * do bound checking (they shouldn't cause buffer overruns),
 * and they always produce null terminated strings.
 *
 * They should be used only with null terminated strings.
 */
void _gnutls_str_cat(char *dest, size_t dest_tot_size, const char *src)
{
	size_t str_size = strlen(src);
	size_t dest_size = strlen(dest);

	if (dest_tot_size - dest_size > str_size) {
		strcat(dest, src);
	} else {
		if (dest_tot_size - dest_size > 0) {
			strncat(dest, src, (dest_tot_size - dest_size) - 1);
			dest[dest_tot_size - 1] = 0;
		}
	}
}

void _gnutls_str_cpy(char *dest, size_t dest_tot_size, const char *src)
{
	size_t str_size = strlen(src);

	if (dest_tot_size > str_size) {
		strcpy(dest, src);
	} else {
		if (dest_tot_size > 0) {
			memcpy(dest, src, (dest_tot_size)-1);
			dest[dest_tot_size - 1] = 0;
		}
	}
}

void _gnutls_buffer_init(gnutls_buffer_st *str)
{
	str->data = str->allocd = NULL;
	str->max_length = 0;
	str->length = 0;
}

void _gnutls_buffer_clear(gnutls_buffer_st *str)
{
	if (str == NULL || str->allocd == NULL)
		return;
	gnutls_free(str->allocd);

	str->data = NULL;
	str->max_length = 0;
	str->length = 0;
}

#define MIN_CHUNK 1024

/**
 * gnutls_buffer_append_data:
 * @dest: the buffer to append to
 * @data: the data
 * @data_size: the size of @data
 *
 * Appends the provided @data to the destination buffer.
 *
 * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code.
 *
 * Since: 3.4.0
 **/
int gnutls_buffer_append_data(gnutls_buffer_t dest, const void *data,
			      size_t data_size)
{
	size_t const tot_len = data_size + dest->length;
	int ret;

	if (unlikely(dest->data != NULL && dest->allocd == NULL))
		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);

	if (data_size == 0)
		return 0;

	if (unlikely(sizeof(size_t) == 4 &&
		     INT_ADD_OVERFLOW(((ssize_t)MAX(data_size, MIN_CHUNK)),
				      ((ssize_t)dest->length)))) {
		return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
	}

	ret = _gnutls_buffer_resize(dest, tot_len);
	if (ret < 0) {
		return ret;
	}
	assert(dest->data != NULL);

	memcpy(&dest->data[dest->length], data, data_size);
	dest->length = tot_len;

	return 0;
}

#ifdef AGGRESSIVE_REALLOC

/* Use a simpler logic for reallocation; i.e., always call
 * gnutls_realloc_fast() and do not reclaim the no-longer-used
 * area which has been removed from the beginning of buffer
 * with _gnutls_buffer_pop_datum().  This helps hit more
 * issues when running under valgrind.
 */
int _gnutls_buffer_resize(gnutls_buffer_st *dest, size_t new_size)
{
	size_t unused;

	if (unlikely(dest->data != NULL && dest->allocd == NULL))
		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);

	unused = MEMSUB(dest->data, dest->allocd);
	dest->allocd = gnutls_realloc_fast(dest->allocd, new_size + unused);
	if (dest->allocd == NULL) {
		gnutls_assert();
		return GNUTLS_E_MEMORY_ERROR;
	}
	dest->max_length = new_size + unused;
	dest->data = dest->allocd + unused;

	return 0;
}

#else

static void align_allocd_with_data(gnutls_buffer_st *dest)
{
	assert(dest->allocd != NULL);
	assert(dest->data != NULL);
	if (dest->length)
		memmove(dest->allocd, dest->data, dest->length);
	dest->data = dest->allocd;
}

int _gnutls_buffer_resize(gnutls_buffer_st *dest, size_t new_size)
{
	if (unlikely(dest->data != NULL && dest->allocd == NULL))
		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);

	if (dest->max_length >= new_size) {
		size_t unused = MEMSUB(dest->data, dest->allocd);
		if (dest->max_length - unused <= new_size) {
			align_allocd_with_data(dest);
		}

		return 0;
	} else {
		size_t unused = MEMSUB(dest->data, dest->allocd);
		size_t alloc_len = MAX(new_size, MIN_CHUNK) +
				   MAX(dest->max_length, MIN_CHUNK);

		dest->allocd = gnutls_realloc_fast(dest->allocd, alloc_len);
		if (dest->allocd == NULL) {
			gnutls_assert();
			return GNUTLS_E_MEMORY_ERROR;
		}
		dest->max_length = alloc_len;
		dest->data = dest->allocd + unused;

		align_allocd_with_data(dest);

		return 0;
	}
}

#endif

/* Appends the provided string. The null termination byte is appended
 * but not included in length.
 */
int _gnutls_buffer_append_str(gnutls_buffer_st *dest, const char *src)
{
	int ret;
	ret = _gnutls_buffer_append_data(dest, src, strlen(src) + 1);
	if (ret >= 0)
		dest->length--;

	return ret;
}

/* returns data from a string in a constant buffer.
 * The data will NOT be valid if buffer is released or
 * data are appended in the buffer.
 */
void _gnutls_buffer_pop_datum(gnutls_buffer_st *str, gnutls_datum_t *data,
			      size_t req_size)
{
	if (str->length == 0) {
		data->data = NULL;
		data->size = 0;
		return;
	}

	if (req_size > str->length)
		req_size = str->length;

	data->data = str->data;
	data->size = req_size;

	str->data += req_size;
	str->length -= req_size;

	/* if string becomes empty start from beginning */
	if (str->length == 0) {
		str->data = str->allocd;
	}

	return;
}

/* converts the buffer to a datum if possible. After this call
 * (failed or not) the buffer should be considered deinitialized.
 */
int _gnutls_buffer_to_datum(gnutls_buffer_st *str, gnutls_datum_t *data,
			    unsigned is_str)
{
	int ret;

	if (str->length == 0) {
		data->data = NULL;
		data->size = 0;
		ret = 0;
		goto fail;
	}

	if (is_str) {
		ret = _gnutls_buffer_append_data(str, "\x00", 1);
		if (ret < 0) {
			gnutls_assert();
			goto fail;
		}
	}

	if (str->allocd != str->data) {
		data->data = gnutls_malloc(str->length);
		if (data->data == NULL) {
			gnutls_assert();
			ret = GNUTLS_E_MEMORY_ERROR;
			goto fail;
		}
		memcpy(data->data, str->data, str->length);
		data->size = str->length;
		_gnutls_buffer_clear(str);
	} else {
		data->data = str->data;
		data->size = str->length;
		_gnutls_buffer_init(str);
	}

	if (is_str) {
		data->size--;
	}

	return 0;
fail:
	_gnutls_buffer_clear(str);
	return ret;
}

/* returns data from a string in a constant buffer. Will
 * fail with GNUTLS_E_PARSING_ERROR, if the string has not enough data.
 */
int _gnutls_buffer_pop_data(gnutls_buffer_st *str, void *data, size_t req_size)
{
	gnutls_datum_t tdata;

	_gnutls_buffer_pop_datum(str, &tdata, req_size);
	if (tdata.data == NULL || tdata.size != req_size) {
		return GNUTLS_E_PARSING_ERROR;
	}

	memcpy(data, tdata.data, tdata.size);

	return 0;
}

int _gnutls_buffer_append_printf(gnutls_buffer_st *dest, const char *fmt, ...)
{
	va_list args;
	int len;
	char *str = NULL;

	va_start(args, fmt);
	len = vasprintf(&str, fmt, args);
	va_end(args);

	if (len < 0 || !str)
		return -1;

	len = _gnutls_buffer_append_str(dest, str);

	free(str);

	return len;
}

static int _gnutls_buffer_insert_data(gnutls_buffer_st *dest, int pos,
				      const void *str, size_t str_size)
{
	size_t orig_length = dest->length;
	int ret;

	ret = _gnutls_buffer_resize(
		dest, dest->length + str_size); /* resize to make space */
	if (ret < 0)
		return ret;

	assert(dest->data != NULL);

	memmove(&dest->data[pos + str_size], &dest->data[pos],
		orig_length - pos);

	memcpy(&dest->data[pos], str, str_size);
	dest->length += str_size;

	return 0;
}

static void _gnutls_buffer_delete_data(gnutls_buffer_st *dest, int pos,
				       size_t str_size)
{
	memmove(&dest->data[pos], &dest->data[pos + str_size],
		dest->length - pos - str_size);

	dest->length -= str_size;

	return;
}

int _gnutls_buffer_append_escape(gnutls_buffer_st *dest, const void *data,
				 size_t data_size, const char *invalid_chars)
{
	int rv = -1;
	char t[5];
	unsigned int pos = dest->length;

	rv = _gnutls_buffer_append_data(dest, data, data_size);
	if (rv < 0)
		return gnutls_assert_val(rv);

	while (pos < dest->length) {
		if (dest->data[pos] == '\\' ||
		    strchr(invalid_chars, dest->data[pos]) ||
		    !c_isgraph(dest->data[pos])) {
			snprintf(t, sizeof(t), "%%%.2X",
				 (unsigned int)dest->data[pos]);

			_gnutls_buffer_delete_data(dest, pos, 1);

			if (_gnutls_buffer_insert_data(dest, pos, t, 3) < 0) {
				rv = -1;
				goto cleanup;
			}
			pos += 3;
		} else
			pos++;
	}

	rv = 0;

cleanup:
	return rv;
}

int _gnutls_buffer_unescape(gnutls_buffer_st *dest)
{
	int rv = -1;
	unsigned int pos = 0;

	while (pos < dest->length) {
		if (dest->data[pos] == '%') {
			if (pos + 1 < dest->length &&
			    dest->data[pos + 1] == '%') {
				// %% -> %
				_gnutls_buffer_delete_data(dest, pos, 1);
			} else if (pos + 2 < dest->length &&
				   c_isxdigit(dest->data[pos + 1]) &&
				   c_isxdigit(dest->data[pos + 2])) {
				unsigned char x;

				hex_decode((char *)dest->data + pos + 1, 2, &x,
					   1);

				_gnutls_buffer_delete_data(dest, pos, 3);
				_gnutls_buffer_insert_data(dest, pos, &x, 1);
			}
		}
		pos++;
	}

	rv = 0;

	return rv;
}

/* Converts the given string (old) to hex. A buffer must be provided
 * to hold the new hex string. The new string will be null terminated.
 * If the buffer does not have enough space to hold the string, a
 * truncated hex string is returned (always null terminated).
 */
char *_gnutls_bin2hex(const void *_old, size_t oldlen, char *buffer,
		      size_t buffer_size, const char *separator)
{
	unsigned int i, j;
	const uint8_t *old = _old;
	int step = 2;
	const char empty[] = "";

	if (separator != NULL && separator[0] != 0)
		step = 3;
	else
		separator = empty;

	if (buffer_size < 3) {
		gnutls_assert();
		return NULL;
	}

	i = j = 0;
	sprintf(&buffer[j], "%.2x", old[i]);
	j += 2;
	i++;

	for (; i < oldlen && j + step < buffer_size; j += step) {
		sprintf(&buffer[j], "%s%.2x", separator, old[i]);
		i++;
	}
	buffer[j] = '\0';

	return buffer;
}

/**
 * gnutls_hex2bin:
 * @hex_data: string with data in hex format
 * @hex_size: size of hex data
 * @bin_data: output array with binary data
 * @bin_size: when calling should hold maximum size of @bin_data,
 *	    on return will hold actual length of @bin_data.
 *
 * Convert a buffer with hex data to binary data. This function
 * unlike gnutls_hex_decode() can parse hex data with separators
 * between numbers. That is, it ignores any non-hex characters.
 *
 * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code.
 *
 * Since: 2.4.0
 **/
int gnutls_hex2bin(const char *hex_data, size_t hex_size, void *bin_data,
		   size_t *bin_size)
{
	return _gnutls_hex2bin(hex_data, hex_size, (void *)bin_data, bin_size);
}

int _gnutls_hex2bin(const char *hex_data, size_t hex_size, uint8_t *bin_data,
		    size_t *bin_size)
{
	unsigned int i, j;
	uint8_t hex2_data[3];
	unsigned long val;

	hex2_data[2] = 0;

	for (i = j = 0; i < hex_size;) {
		if (!isxdigit(
			    hex_data[i])) { /* skip non-hex such as the ':' in 00:FF */
			i++;
			continue;
		}
		if (j >= *bin_size) {
			gnutls_assert();
			return GNUTLS_E_SHORT_MEMORY_BUFFER;
		}

		if (i + 1 >= hex_size)
			return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);

		hex2_data[0] = hex_data[i];
		hex2_data[1] = hex_data[i + 1];
		i += 2;

		val = strtoul((char *)hex2_data, NULL, 16);
		if (val == ULONG_MAX) {
			gnutls_assert();
			return GNUTLS_E_PARSING_ERROR;
		}
		bin_data[j] = val;
		j++;
	}
	*bin_size = j;

	return 0;
}

/**
 * gnutls_hex_decode2:
 * @hex_data: contain the encoded data
 * @result: the result in an allocated string
 *
 * This function will decode the given encoded data, using the hex
 * encoding used by PSK password files.
 *
 * Returns: %GNUTLS_E_PARSING_ERROR on invalid hex data, or 0 on success.
 **/
int gnutls_hex_decode2(const gnutls_datum_t *hex_data, gnutls_datum_t *result)
{
	int ret;
	int size = hex_data_size(hex_data->size);

	result->data = gnutls_malloc(size);
	if (result->data == NULL) {
		gnutls_assert();
		return GNUTLS_E_MEMORY_ERROR;
	}

	result->size = size;
	ret = hex_decode((char *)hex_data->data, hex_data->size, result->data,
			 result->size);
	if (ret == 0) {
		gnutls_assert();
		gnutls_free(result->data);
		return GNUTLS_E_PARSING_ERROR;
	}

	return 0;
}

/**
 * gnutls_hex_decode:
 * @hex_data: contain the encoded data
 * @result: the place where decoded data will be copied
 * @result_size: holds the size of the result
 *
 * This function will decode the given encoded data, using the hex
 * encoding used by PSK password files.
 *
 * Initially @result_size must hold the maximum size available in
 * @result, and on return it will contain the number of bytes written.
 *
 * Returns: %GNUTLS_E_SHORT_MEMORY_BUFFER if the buffer given is not
 *   long enough, %GNUTLS_E_PARSING_ERROR on invalid hex data, or 0 on success.
 **/
int gnutls_hex_decode(const gnutls_datum_t *hex_data, void *result,
		      size_t *result_size)
{
	int ret;
	size_t size = hex_data_size(hex_data->size);

	if (*result_size < size) {
		gnutls_assert();
		return GNUTLS_E_SHORT_MEMORY_BUFFER;
	}

	ret = hex_decode((char *)hex_data->data, hex_data->size, result, size);
	if (ret == 0) {
		return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);
	}
	*result_size = size;

	return 0;
}

/**
 * gnutls_hex_encode:
 * @data: contain the raw data
 * @result: the place where hex data will be copied
 * @result_size: holds the size of the result
 *
 * This function will convert the given data to printable data, using
 * the hex encoding, as used in the PSK password files.
 *
 * Note that the size of the result includes the null terminator.
 *
 * Returns: %GNUTLS_E_SHORT_MEMORY_BUFFER if the buffer given is not
 * long enough, or 0 on success.
 **/
int gnutls_hex_encode(const gnutls_datum_t *data, char *result,
		      size_t *result_size)
{
	int ret;
	size_t size = hex_str_size(data->size);

	if (*result_size < size) {
		gnutls_assert();
		return GNUTLS_E_SHORT_MEMORY_BUFFER;
	}

	ret = hex_encode(data->data, data->size, result, *result_size);
	if (ret == 0) {
		return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);
	}

	*result_size = size;

	return 0;
}

/**
 * gnutls_hex_encode2:
 * @data: contain the raw data
 * @result: the result in an allocated string
 *
 * This function will convert the given data to printable data, using
 * the hex encoding, as used in the PSK password files.
 *
 * Note that the size of the result does NOT include the null terminator.
 *
 * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code.
 **/
int gnutls_hex_encode2(const gnutls_datum_t *data, gnutls_datum_t *result)
{
	int ret;
	int size = hex_str_size(data->size);

	result->data = gnutls_malloc(size);
	if (result->data == NULL) {
		gnutls_assert();
		return GNUTLS_E_MEMORY_ERROR;
	}

	ret = hex_encode((char *)data->data, data->size, (char *)result->data,
			 size);
	if (ret == 0) {
		gnutls_free(result->data);
		return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);
	}

	result->size = size - 1;

	return 0;
}

static int hostname_compare_raw(const char *certname, size_t certnamesize,
				const char *hostname)
{
	if (certnamesize == strlen(hostname) &&
	    memcmp(hostname, certname, certnamesize) == 0)
		return 1;
	return 0;
}

static int hostname_compare_ascii(const char *certname, size_t certnamesize,
				  const char *hostname)
{
	for (; *certname && *hostname &&
	       c_toupper(*certname) == c_toupper(*hostname);
	     certname++, hostname++, certnamesize--)
		;

	/* the strings are the same */
	if (certnamesize == 0 && *hostname == '\0')
		return 1;

	return 0;
}

/* compare hostname against certificate, taking account of wildcards
 * return 1 on success or 0 on error
 *
 * note: certnamesize is required as X509 certs can contain embedded NULs in
 * the strings such as CN or subjectAltName.
 *
 * Wildcards are taken into account only if they are the leftmost
 * component, and if the string is ascii only (partial advice from rfc6125)
 *
 */
int _gnutls_hostname_compare(const char *certname, size_t certnamesize,
			     const char *hostname, unsigned vflags)
{
	char *p;
	unsigned i;

	for (i = 0; i < certnamesize; i++) {
		if (c_isprint(certname[i]) == 0)
			return hostname_compare_raw(certname, certnamesize,
						    hostname);
	}

	if (*certname == '*' &&
	    !(vflags & GNUTLS_VERIFY_DO_NOT_ALLOW_WILDCARDS)) {
		/* a wildcard certificate */

		/* ensure that we have at least two domain components after
		 * the wildcard. */
		p = strrchr(certname, '.');
		if (p == NULL || strchr(certname, '.') == p || p[1] == 0) {
			return 0;
		}

		certname++;
		certnamesize--;

		while (1) {
			if (hostname_compare_ascii(certname, certnamesize,
						   hostname))
				return 1;

			/* wildcards are only allowed to match a single domain
			   component or component fragment */
			if (*hostname == '\0' || *hostname == '.')
				break;
			hostname++;
		}

		return 0;
	} else {
		return hostname_compare_ascii(certname, certnamesize, hostname);
	}
}

int _gnutls_buffer_append_prefix(gnutls_buffer_st *buf, int pfx_size,
				 size_t data_size)
{
	uint8_t ss[4];

	if (pfx_size == 32) {
		_gnutls_write_uint32(data_size, ss);
		pfx_size = 4;
	} else if (pfx_size == 24) {
		_gnutls_write_uint24(data_size, ss);
		pfx_size = 3;
	} else if (pfx_size == 16) {
		_gnutls_write_uint16(data_size, ss);
		pfx_size = 2;
	} else if (pfx_size == 8) {
		ss[0] = data_size;
		pfx_size = 1;
	} else
		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);

	return _gnutls_buffer_append_data(buf, ss, pfx_size);
}

int _gnutls_buffer_pop_prefix8(gnutls_buffer_st *buf, uint8_t *data, int check)
{
	if (buf->length < 1) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	*data = buf->data[0];

	if (check && *data > buf->length - 1) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	buf->data++;
	buf->length--;

	return 0;
}

int _gnutls_buffer_pop_prefix16(gnutls_buffer_st *buf, size_t *data_size,
				int check)
{
	size_t size;

	if (buf->length < 2) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	size = _gnutls_read_uint16(buf->data);
	if (check && size > buf->length - 2) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	buf->data += 2;
	buf->length -= 2;

	*data_size = size;

	return 0;
}

int _gnutls_buffer_pop_prefix24(gnutls_buffer_st *buf, size_t *data_size,
				int check)
{
	size_t size;

	if (buf->length < 3) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	size = _gnutls_read_uint24(buf->data);
	if (check && size > buf->length - 3) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	buf->data += 3;
	buf->length -= 3;

	*data_size = size;

	return 0;
}

/* Reads an uint32 number from the buffer. If check is non zero it will also check whether
 * the number read, is less than the data in the buffer
 */
int _gnutls_buffer_pop_prefix32(gnutls_buffer_st *buf, size_t *data_size,
				int check)
{
	size_t size;

	if (buf->length < 4) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	size = _gnutls_read_uint32(buf->data);
	if (check && size > buf->length - 4) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	buf->data += 4;
	buf->length -= 4;

	*data_size = size;

	return 0;
}

int _gnutls_buffer_pop_datum_prefix32(gnutls_buffer_st *buf,
				      gnutls_datum_t *data)
{
	size_t size;
	int ret;

	ret = _gnutls_buffer_pop_prefix32(buf, &size, 1);
	if (ret < 0) {
		gnutls_assert();
		return ret;
	}

	if (size > 0) {
		size_t osize = size;
		_gnutls_buffer_pop_datum(buf, data, size);
		if (osize != data->size) {
			gnutls_assert();
			return GNUTLS_E_PARSING_ERROR;
		}
	} else {
		data->size = 0;
		data->data = NULL;
	}

	return 0;
}

int _gnutls_buffer_pop_datum_prefix24(gnutls_buffer_st *buf,
				      gnutls_datum_t *data)
{
	size_t size;
	int ret;

	ret = _gnutls_buffer_pop_prefix24(buf, &size, 1);
	if (ret < 0) {
		gnutls_assert();
		return ret;
	}

	if (size > 0) {
		size_t osize = size;
		_gnutls_buffer_pop_datum(buf, data, size);
		if (osize != data->size) {
			gnutls_assert();
			return GNUTLS_E_PARSING_ERROR;
		}
	} else {
		data->size = 0;
		data->data = NULL;
	}

	return 0;
}

int _gnutls_buffer_pop_datum_prefix16(gnutls_buffer_st *buf,
				      gnutls_datum_t *data)
{
	size_t size;

	if (buf->length < 2) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	size = _gnutls_read_uint16(buf->data);

	buf->data += 2;
	buf->length -= 2;

	if (size > 0) {
		size_t osize = size;
		_gnutls_buffer_pop_datum(buf, data, size);
		if (osize != data->size) {
			gnutls_assert();
			return GNUTLS_E_PARSING_ERROR;
		}
	} else {
		data->size = 0;
		data->data = NULL;
	}

	return 0;
}

int _gnutls_buffer_pop_datum_prefix8(gnutls_buffer_st *buf,
				     gnutls_datum_t *data)
{
	size_t size;

	if (buf->length < 1) {
		gnutls_assert();
		return GNUTLS_E_PARSING_ERROR;
	}

	size = buf->data[0];

	buf->data++;
	buf->length--;

	if (size > 0) {
		size_t osize = size;
		_gnutls_buffer_pop_datum(buf, data, size);
		if (osize != data->size) {
			gnutls_assert();
			return GNUTLS_E_PARSING_ERROR;
		}
	} else {
		data->size = 0;
		data->data = NULL;
	}

	return 0;
}

int _gnutls_buffer_append_data_prefix(gnutls_buffer_st *buf, int pfx_size,
				      const void *data, size_t data_size)
{
	int ret;

	ret = _gnutls_buffer_append_prefix(buf, pfx_size, data_size);
	if (ret < 0)
		return gnutls_assert_val(ret);

	if (data_size > 0) {
		ret = _gnutls_buffer_append_data(buf, data, data_size);
		if (ret < 0)
			return gnutls_assert_val(ret);
	}

	return 0;
}

int _gnutls_buffer_append_mpi(gnutls_buffer_st *buf, int pfx_size, bigint_t mpi,
			      int lz)
{
	gnutls_datum_t dd;
	int ret;

	if (lz)
		ret = _gnutls_mpi_dprint_lz(mpi, &dd);
	else
		ret = _gnutls_mpi_dprint(mpi, &dd);

	if (ret < 0)
		return gnutls_assert_val(ret);

	ret = _gnutls_buffer_append_data_prefix(buf, pfx_size, dd.data,
						dd.size);

	_gnutls_free_datum(&dd);

	return ret;
}

/* Appends an MPI of fixed-size in bytes left-padded with zeros if necessary */
int _gnutls_buffer_append_fixed_mpi(gnutls_buffer_st *buf, bigint_t mpi,
				    unsigned size)
{
	gnutls_datum_t dd;
	unsigned pad, i;
	int ret;

	ret = _gnutls_mpi_dprint(mpi, &dd);
	if (ret < 0)
		return gnutls_assert_val(ret);

	if (size < dd.size) {
		ret = gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
		goto cleanup;
	}

	pad = size - dd.size;
	for (i = 0; i < pad; i++) {
		ret = _gnutls_buffer_append_data(buf, "\x00", 1);
		if (ret < 0) {
			gnutls_assert();
			goto cleanup;
		}
	}

	/* append the rest */
	ret = _gnutls_buffer_append_data(buf, dd.data, dd.size);

cleanup:
	_gnutls_free_datum(&dd);
	return ret;
}

void _gnutls_buffer_hexprint(gnutls_buffer_st *str, const void *_data,
			     size_t len)
{
	size_t j;
	const unsigned char *data = _data;

	if (len == 0)
		_gnutls_buffer_append_str(str, "00");
	else {
		for (j = 0; j < len; j++)
			_gnutls_buffer_append_printf(str, "%.2x",
						     (unsigned)data[j]);
	}
}

int _gnutls_buffer_base64print(gnutls_buffer_st *str, const void *_data,
			       size_t len)
{
	const unsigned char *data = _data;
	unsigned b64len = BASE64_ENCODE_RAW_LENGTH(len);
	int ret;

	ret = _gnutls_buffer_resize(str, str->length + b64len + 1);
	if (ret < 0) {
		return gnutls_assert_val(ret);
	}

	base64_encode_raw((void *)&str->data[str->length], len, data);
	str->length += b64len;
	str->data[str->length] = 0;

	return 0;
}

void _gnutls_buffer_hexdump(gnutls_buffer_st *str, const void *_data,
			    size_t len, const char *spc)
{
	size_t j;
	const unsigned char *data = _data;

	if (spc)
		_gnutls_buffer_append_str(str, spc);
	for (j = 0; j < len; j++) {
		if (((j + 1) % 16) == 0) {
			_gnutls_buffer_append_printf(str, "%.2x\n",
						     (unsigned)data[j]);
			if (spc && j != (len - 1))
				_gnutls_buffer_append_str(str, spc);
		} else if (j == (len - 1))
			_gnutls_buffer_append_printf(str, "%.2x",
						     (unsigned)data[j]);
		else
			_gnutls_buffer_append_printf(
				str, "%.2x:", (unsigned)data[j]);
	}
	if ((j % 16) != 0)
		_gnutls_buffer_append_str(str, "\n");
}

void _gnutls_buffer_asciiprint(gnutls_buffer_st *str, const char *data,
			       size_t len)
{
	size_t j;

	for (j = 0; j < len; j++)
		if (c_isprint(data[j]))
			_gnutls_buffer_append_printf(str, "%c",
						     (unsigned char)data[j]);
		else
			_gnutls_buffer_append_printf(str, ".");
}
