/*
 * print.c	Routines to print stuff.
 *
 * Version:	$Id: 09f7db7df0827a38e1606e0934332097e974837b $
 *
 *   This library 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 library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Copyright 2000,2006  The FreeRADIUS server project
 */

RCSID("$Id: 09f7db7df0827a38e1606e0934332097e974837b $")

#include	<freeradius-devel/libradius.h>

#include	<ctype.h>

/*
 *	Checks for utf-8, taken from:
 *
 *  http://www.w3.org/International/questions/qa-forms-utf-8
 *
 *	Note that we don't care about the length of the input string,
 *	because '\0' is an invalid UTF-8 character.
 */
int fr_utf8_char(uint8_t const *str)
{
	if (*str < 0x20) return 0;

	if (*str <= 0x7e) return 1; /* 1 */

	if (*str <= 0xc1) return 0;

	if ((str[0] >= 0xc2) &&	/* 2 */
	    (str[0] <= 0xdf) &&
	    (str[1] >= 0x80) &&
	    (str[1] <= 0xbf)) {
		return 2;
	}

	if ((str[0] == 0xe0) &&	/* 3 */
	    (str[1] >= 0xa0) &&
	    (str[1] <= 0xbf) &&
	    (str[2] >= 0x80) &&
	    (str[2] <= 0xbf)) {
		return 3;
	}

	if ((str[0] >= 0xe1) &&	/* 4a */
	    (str[0] <= 0xec) &&
	    (str[1] >= 0x80) &&
	    (str[1] <= 0xbf) &&
	    (str[2] >= 0x80) &&
	    (str[2] <= 0xbf)) {
		return 3;
	}

	if ((str[0] >= 0xee) &&	/* 4b */
	    (str[0] <= 0xef) &&
	    (str[1] >= 0x80) &&
	    (str[1] <= 0xbf) &&
	    (str[2] >= 0x80) &&
	    (str[2] <= 0xbf)) {
		return 3;
	}

	if ((str[0] == 0xed) &&	/* 5 */
	    (str[1] >= 0x80) &&
	    (str[1] <= 0x9f) &&
	    (str[2] >= 0x80) &&
	    (str[2] <= 0xbf)) {
		return 3;
	}

	if ((str[0] == 0xf0) &&	/* 6 */
	    (str[1] >= 0x90) &&
	    (str[1] <= 0xbf) &&
	    (str[2] >= 0x80) &&
	    (str[2] <= 0xbf) &&
	    (str[3] >= 0x80) &&
	    (str[3] <= 0xbf)) {
		return 4;
	}

	if ((str[0] >= 0xf1) &&	/* 6 */
	    (str[1] <= 0xf3) &&
	    (str[1] >= 0x80) &&
	    (str[1] <= 0xbf) &&
	    (str[2] >= 0x80) &&
	    (str[2] <= 0xbf) &&
	    (str[3] >= 0x80) &&
	    (str[3] <= 0xbf)) {
		return 4;
	}


	if ((str[0] == 0xf4) &&	/* 7 */
	    (str[1] >= 0x80) &&
	    (str[1] <= 0x8f) &&
	    (str[2] >= 0x80) &&
	    (str[2] <= 0xbf) &&
	    (str[3] >= 0x80) &&
	    (str[3] <= 0xbf)) {
		return 4;
	}

	/*
	 *	Invalid UTF-8 Character
	 */
	return 0;
}

/*
 *	Convert a string to something printable.  The output string
 *	has to be larger than the input string by at least 5 bytes.
 *	If not, the output is silently truncated...
 */
size_t fr_print_string(char const *in, size_t inlen, char *out, size_t outlen)
{
	uint8_t const	*p = (uint8_t const *) in;
	int		sp = 0;
	int		utf8 = 0;
	size_t		freespace = outlen;

	if (!in) {
		if (outlen) *out = '\0';

		return 0;
	}

	if (inlen == 0) inlen = strlen(in);

	while ((inlen > 0) && (freespace > 4)) {
		/*
		 *	Hack: never print trailing zero.
		 *	Some clients send pings with an off-by-one
		 *	length (confused with strings in C).
		 */
		if ((inlen == 1) && (*p == '\0')) {
			inlen--;
			break;
		}
		switch (*p) {
			case '\\':
				sp = '\\';
				break;
			case '\r':
				sp = 'r';
				break;
			case '\n':
				sp = 'n';
				break;
			case '\t':
				sp = 't';
				break;
			case '"':
				sp = '"';
				break;
			default:
				sp = '\0';
				break;
		}

		if (sp) {
			*out++ = '\\';
			*out++ = sp;
			freespace -= 2;
			p++;
			inlen--;
			continue;
		}

		utf8 = fr_utf8_char(p);
		if (!utf8) {
			snprintf(out, freespace, "\\%03o", *p);
			out += 4;
			freespace -= 4;
			p++;
			inlen--;
			continue;
		}

		do {
			*out++ = *p++;
			freespace--;
			inlen--;
		} while (--utf8 > 0);
	}
	*out = '\0';

	if (inlen > 0) return outlen + 4;
	return outlen - freespace;
}


/** Print the value of an attribute to a string
 *
 * @param[out] out Where to write the string.
 * @param[in] outlen Size of outlen (must be at least 3 bytes).
 * @param[in] vp to print.
 * @param[in] quote Char to add before and after printed value, if 0 no char will be added, if < 0 raw string will be
 *	added.
 * @return the length of data written to out, or a value >= outlen on truncation.
 */
size_t vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, int8_t quote)
{
	DICT_VALUE	*v;
	char		buf[1024];	/* Interim buffer to use with poorly behaved printing functions */
	char const	*a = NULL;
	time_t		t;
	struct tm	s_tm;

	size_t		len = 0, freespace = outlen;

	*out = '\0';

	if (!vp) return 0;

	switch (vp->da->type) {
	case PW_TYPE_STRING:
		/* need to copy the escaped value, but quoted */
		if (quote > 0) {
			if (freespace < 3) {
				return vp->length + 2;
			}

			*out++ = (char) quote;
			freespace--;

			len = fr_print_string(vp->vp_strvalue, vp->length, out, freespace);
			/* always terminate the quoted string with another quote */
			if (len >= (freespace - 1)) {
				out[outlen - 2] = (char) quote;
				out[outlen - 1] = '\0';
				return len + 2;
			}
			out += len;
			freespace -= len;

			*out++ = (char) quote;
			freespace--;
			*out = '\0';

			return len + 2;
		}

		/* xlat.c - need to copy raw value verbatim */
		else if (quote < 0) {
			return strlcpy(out, vp->vp_strvalue, outlen);
		}

		return fr_print_string(vp->vp_strvalue, vp->length, out, outlen);

	case PW_TYPE_INTEGER:
		if (vp->da->flags.has_tag) {
			/* Attribute value has a tag, need to ignore it */
			if ((v = dict_valbyattr(vp->da->attr, vp->da->vendor, (vp->vp_integer & 0xffffff))) != NULL) {
				a = v->name;
				len = strlen(a);
			} else {
				/* should never be truncated */
				len = snprintf(buf, sizeof(buf), "%u", (vp->vp_integer & 0xffffff));
				a = buf;
			}
		} else {
	case PW_TYPE_BYTE:
	case PW_TYPE_SHORT:
			/* Normal, non-tagged attribute */
			if ((v = dict_valbyattr(vp->da->attr, vp->da->vendor, vp->vp_integer)) != NULL) {
				a = v->name;
				len = strlen(a);
			} else {
				/* should never be truncated */
				len = snprintf(buf, sizeof(buf), "%u", vp->vp_integer);
				a = buf;
			}
		}
		break;

	case PW_TYPE_INTEGER64:
		return snprintf(out, outlen, "%" PRIu64, vp->vp_integer64);

	case PW_TYPE_DATE:
		t = vp->vp_date;
		if (quote > 0) {
			len = strftime(buf, sizeof(buf) - 1, "%%%b %e %Y %H:%M:%S %Z%%", localtime_r(&t, &s_tm));
			buf[0] = (char) quote;
			buf[len - 1] = (char) quote;
			buf[len] = '\0';
		} else {
			len = strftime(buf, sizeof(buf), "%b %e %Y %H:%M:%S %Z", localtime_r(&t, &s_tm));
		}
		a = buf;
		break;

	case PW_TYPE_SIGNED: /* Damned code for 1 WiMAX attribute */
		len = snprintf(buf, sizeof(buf), "%d", vp->vp_signed);
		a = buf;
		break;

	case PW_TYPE_IPADDR:
		a = inet_ntop(AF_INET, &(vp->vp_ipaddr), buf, sizeof(buf));
		len = strlen(buf);
		break;

	case PW_TYPE_ABINARY:
#ifdef WITH_ASCEND_BINARY
		print_abinary(vp, buf, sizeof(buf), quote);
		a = buf;
		len = strlen(buf);
		break;
#else
	/* FALL THROUGH */
#endif
	case PW_TYPE_OCTETS:
	case PW_TYPE_TLV:
	{
		size_t max;

		/* Return the number of bytes we would of written */
		len = (vp->length * 2) + 2;
		if (freespace <= 1) {
			return len;
		}

		*out++ = '0';
		freespace--;

		if (freespace <= 1) {
			*out = '\0';
			return len;
		}
		*out++ = 'x';
		freespace--;

		if (freespace <= 2) {
			*out = '\0';
			return len;
		}

		/* Get maximum number of bytes we can encode given freespace */
		max = ((freespace % 2) ? freespace - 1 : freespace - 2) / 2;
		fr_bin2hex(out, vp->vp_octets, (vp->length > max) ? max : vp->length);

		return len;
	}
		break;

	case PW_TYPE_IFID:
		a = ifid_ntoa(buf, sizeof(buf), vp->vp_ifid);
		len = strlen(buf);
		break;

	case PW_TYPE_IPV6ADDR:
		a = inet_ntop(AF_INET6, &vp->vp_ipv6addr, buf, sizeof(buf));
		len = strlen(buf);
		break;

	case PW_TYPE_IPV6PREFIX:
	{
		struct in6_addr addr;

		/*
		 *	Alignment issues.
		 */
		memcpy(&addr, &(vp->vp_ipv6prefix[2]), sizeof(addr));

		a = inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
		if (a) {
			char *p = buf;

			len = strlen(buf);
			p += len;
			len += snprintf(p, sizeof(buf) - len, "/%u", (unsigned int) vp->vp_ipv6prefix[1]);
		}
	}
		break;

	case PW_TYPE_IPV4PREFIX:
	{
		struct in_addr addr;

		/*
		 *	Alignment issues.
		 */
		memcpy(&addr, &(vp->vp_ipv4prefix[2]), sizeof(addr));

		a = inet_ntop(AF_INET, &addr, buf, sizeof(buf));
		if (a) {
			char *p = buf;

			len = strlen(buf);
			p += len;
			len += snprintf(p, sizeof(buf) - len, "/%u", (unsigned int) (vp->vp_ipv4prefix[1] & 0x3f));
		}
	}
		break;

	case PW_TYPE_ETHERNET:
		return snprintf(out, outlen, "%02x:%02x:%02x:%02x:%02x:%02x",
				vp->vp_ether[0], vp->vp_ether[1],
				vp->vp_ether[2], vp->vp_ether[3],
				vp->vp_ether[4], vp->vp_ether[5]);

	default:
		a = "UNKNOWN-TYPE";
		len = strlen(a);
		break;
	}

	if (a) strlcpy(out, a, outlen);

	return len;	/* Return the number of bytes we would of written (for truncation detection) */
}

char *vp_aprinttype(TALLOC_CTX *ctx, PW_TYPE type)
{
	switch (type) {
	case PW_TYPE_STRING :
		return talloc_strdup(ctx, "_");

	case PW_TYPE_INTEGER64:
	case PW_TYPE_SIGNED:
	case PW_TYPE_BYTE:
	case PW_TYPE_SHORT:
	case PW_TYPE_INTEGER:
	case PW_TYPE_DATE :
		return talloc_strdup(ctx, "0");

	case PW_TYPE_IPADDR :
		return talloc_strdup(ctx, "?.?.?.?");

	case PW_TYPE_IPV4PREFIX:
		return talloc_strdup(ctx, "?.?.?.?/?");

	case PW_TYPE_IPV6ADDR:
		return talloc_strdup(ctx, "[:?:]");

	case PW_TYPE_IPV6PREFIX:
		return talloc_strdup(ctx, "[:?:]/?");

	case PW_TYPE_OCTETS:
		return talloc_strdup(ctx, "0x??");

	case PW_TYPE_ETHERNET:
		return talloc_strdup(ctx, "??:??:??:??:??:??:??:??");

#ifdef WITH_ASCEND_BINARY
	case PW_TYPE_ABINARY:
		return talloc_strdup(ctx, "??");
#endif

	default :
		break;
	}

	return talloc_strdup(ctx, "<UNKNOWN-TYPE>");
}

/*
 *	vp_prints_value for talloc
 */
char *vp_aprint(TALLOC_CTX *ctx, VALUE_PAIR const *vp)
{
	char *p;

	switch (vp->da->type) {
	case PW_TYPE_STRING:
		/*
		 *	FIXME: deal with \r\n" ??
		 */
		p = talloc_strdup(ctx, vp->vp_strvalue);
		break;

	case PW_TYPE_BYTE:
	case PW_TYPE_SHORT:
	case PW_TYPE_INTEGER:
		{
			DICT_VALUE *dv;

			dv = dict_valbyattr(vp->da->attr, vp->da->vendor,
					    vp->vp_integer);
			if (dv) {
				p = talloc_strdup(ctx, dv->name);
			} else {
				p = talloc_asprintf(ctx, "%u", vp->vp_integer);
			}
		}
		break;

	case PW_TYPE_SIGNED:
		p = talloc_asprintf(ctx, "%d", vp->vp_signed);
		break;

	case PW_TYPE_INTEGER64:
		p = talloc_asprintf(ctx, "%" PRIu64 , vp->vp_integer64);
		break;

	case PW_TYPE_ETHERNET:
		p = talloc_asprintf(ctx, "%02x:%02x:%02x:%02x:%02x:%02x",
				    vp->vp_ether[0], vp->vp_ether[1],
				    vp->vp_ether[2], vp->vp_ether[3],
				    vp->vp_ether[4], vp->vp_ether[5]);
		break;

	case PW_TYPE_ABINARY:
#ifdef WITH_ASCEND_BINARY
		p = talloc_array(ctx, char, 128);
		if (!p) return NULL;
		print_abinary(vp, p, 128, 0);
		break;
#else
		  /* FALL THROUGH */
#endif

	case PW_TYPE_OCTETS:
		p = talloc_array(ctx, char, 3 + vp->length * 2);
		if (!p) return NULL;
		memcpy(p, "0x", 2);
		fr_bin2hex(p + 2, vp->vp_octets, vp->length);
		break;

	case PW_TYPE_DATE:
	{
		time_t t;
		struct tm s_tm;

		t = vp->vp_date;

		p = talloc_array(ctx, char, 64);
		strftime(p, 64, "%b %e %Y %H:%M:%S %Z",
			 localtime_r(&t, &s_tm));
		break;
	}

	/*
	 *	We need to use the proper inet_ntop functions for IP
	 *	addresses, else the output might not match output of
	 *	other functions, which makes testing difficult.
	 *
	 *	An example is tunnelled ipv4 in ipv6 addresses.
	 */
	case PW_TYPE_IPADDR:
	case PW_TYPE_IPV4PREFIX:
		{
			char buff[INET_ADDRSTRLEN  + 4]; // + /prefix

			buff[0] = '\0';
			vp_prints_value(buff, sizeof(buff), vp, 0);

			p = talloc_strdup(ctx, buff);
		}
		break;

	case PW_TYPE_IPV6ADDR:
	case PW_TYPE_IPV6PREFIX:
		{
			char buff[INET6_ADDRSTRLEN + 4]; // + /prefix

			buff[0] = '\0';
			vp_prints_value(buff, sizeof(buff), vp, 0);

			p = talloc_strdup(ctx, buff);
		}
		break;

	case PW_TYPE_IFID:
		p = talloc_asprintf(ctx, "%x:%x:%x:%x",
				    (vp->vp_ifid[0] << 8) | vp->vp_ifid[1],
				    (vp->vp_ifid[2] << 8) | vp->vp_ifid[3],
				    (vp->vp_ifid[4] << 8) | vp->vp_ifid[5],
				    (vp->vp_ifid[6] << 8) | vp->vp_ifid[7]);
		break;

	default:
		p = NULL;
		break;
	}

	return p;
}


/**  Prints attribute values escaped suitably for use as JSON values
 *
 *  Returns < 0 if the buffer may be (or have been) too small to write the encoded
 *  JSON value to.
 *
 * @param out Where to write the string.
 * @param outlen Lenth of output buffer.
 * @param vp to print.
 * @return the length of data written to out, or a value >= outlen on truncation.
 */
size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp)
{
	char const	*q;
	size_t		len, freespace = outlen;

	if (!vp->da->flags.has_tag) {
		switch (vp->da->type) {
			case PW_TYPE_INTEGER:
			case PW_TYPE_BYTE:
			case PW_TYPE_SHORT:
				if (vp->da->flags.has_value) break;

				return snprintf(out, freespace, "%u", vp->vp_integer);

			case PW_TYPE_SIGNED:
				return snprintf(out, freespace, "%d", vp->vp_signed);

			default:
				break;
		}
	}

	/* Indicate truncation */
	if (freespace < 2) return outlen + 1;
	*out++ = '"';
	freespace--;

	switch (vp->da->type) {
		case PW_TYPE_STRING:
			for (q = vp->vp_strvalue; q < vp->vp_strvalue + vp->length; q++) {
				/* Indicate truncation */
				if (freespace < 3) return outlen + 1;

				if (*q == '"') {
					*out++ = '\\';
					*out++ = '"';
					freespace -= 2;
				} else if (*q == '\\') {
					*out++ = '\\';
					*out++ = '\\';
					freespace -= 2;
				} else if (*q == '/') {
					*out++ = '\\';
					*out++ = '/';
					freespace -= 2;
				} else if (*q >= ' ') {
					*out++ = *q;
					freespace--;
				} else {
					*out++ = '\\';
					freespace--;

					switch (*q) {
					case '\b':
						*out++ = 'b';
						freespace--;
						break;

					case '\f':
						*out++ = 'f';
						freespace--;
						break;

					case '\n':
						*out++ = 'b';
						freespace--;
						break;

					case '\r':
						*out++ = 'r';
						freespace--;
						break;

					case '\t':
						*out++ = 't';
						freespace--;
						break;
					default:
						len = snprintf(out, freespace, "u%04X", *q);
						if (is_truncated(len, freespace)) return (outlen - freespace) + len;
						out += len;
						freespace -= len;
					}
				}
			}
			break;

		default:
			len = vp_prints_value(out, freespace, vp, 0);
			if (is_truncated(len, freespace)) return (outlen - freespace) + len;
			out += len;
			freespace -= len;
			break;
	}

	/* Indicate truncation */
	if (freespace < 2) return outlen + 1;
	*out++ = '"';
	*out = '\0'; // We don't increment out, because the nul byte should not be included in the length

	return outlen - freespace;
}

/*
 *  This is a hack, and has to be kept in sync with tokens.h
 */
static char const *vp_tokens[] = {
  "?",				/* T_OP_INVALID */
  "EOL",			/* T_EOL */
  "{",
  "}",
  "(",
  ")",
  ",",
  ";",
  "++",
  "+=",
  "-=",
  ":=",
  "=",
  "!=",
  ">=",
  ">",
  "<=",
  "<",
  "=~",
  "!~",
  "=*",
  "!*",
  "==",
  "#",
  "<BARE-WORD>",
  "<\"STRING\">",
  "<'STRING'>",
  "<`STRING`>"
};

extern int fr_attr_max_tlv;
extern int fr_attr_shift[];
extern int fr_attr_mask[];

/** Print an attribute OID (does not include vendor)
 *
 * @param out Where to write the string.
 * @param outlen Lenth of output buffer.
 * @param attr id.
 * @param dv_type Type of dictionary value.
 * @return the length of data written to out, or a value >= outlen on truncation.
 */
static size_t vp_print_attr_oid(char *out, size_t outlen, unsigned int attr, int dv_type)
{
	int		nest;
	size_t		len, freespace = outlen;

	switch (dv_type) {
	case 4:
		return snprintf(out, freespace, "%u", attr);

	case 2:
		return snprintf(out, freespace, "%u", attr & 0xffff);

	default:
	case 1:
		len = snprintf(out, freespace, "%u", attr & 0xff);
		if (is_truncated(len, freespace)) return len;
		out += len;
		freespace -= len;
		break;
	}

	if ((attr >> 8) == 0) return (outlen - freespace);

	for (nest = 1; nest <= fr_attr_max_tlv; nest++) {
		if (((attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]) == 0) break;

		len = snprintf(out, freespace, ".%u", (attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]);
		if (is_truncated(len, freespace)) return (outlen - freespace) + len;
		out += len;
		freespace -= len;
	}

	return (outlen - freespace);
}

/** Print the names of attributes which are not in the dictionaries
 *
 * Print name for an unknown attribute in the format:
@verbatim
	Attr-<vendor id>-<attribute oid>
@endverbatim
 * to a string.
 *
 * @param out Where to write the string.
 * @param outlen Lenth of output buffer.
 * @param attr id.
 * @param vendor id.
 * @return the length of data written to out, or a value >= outlen on truncation.
 */
size_t vp_print_name(char *out, size_t outlen, unsigned int attr, unsigned int vendor)
{
	int 		dv_type = 1;
	size_t		len, freespace = outlen;

	if (!out) return 0;

	len = snprintf(out, freespace, "Attr-");
	if (is_truncated(len, freespace)) {
		return len;
	}
	out += len;
	freespace -= len;

	if (vendor > FR_MAX_VENDOR) {
		len = snprintf(out, freespace, "%u.", vendor / FR_MAX_VENDOR);
		if (is_truncated(len, freespace)) return (outlen - freespace) + len;
		out += len;
		freespace -= len;

		vendor &= (FR_MAX_VENDOR) - 1;
	}

	if (vendor) {
		DICT_VENDOR *dv;

		dv = dict_vendorbyvalue(vendor);
		if (dv) {
			dv_type = dv->type;
		}

		len = snprintf(out, freespace, "26.%u.", vendor);
		if (is_truncated(len, freespace)) return (outlen - freespace) + len;
		out += len;
		freespace -= len;
	}

	len = vp_print_attr_oid(out, freespace, attr, dv_type);
	if (is_truncated(len, freespace)) return (outlen - freespace) + len;
	freespace -= len;

	return (outlen - freespace);
}


/** Print one attribute and value to a string
 *
 * Print a VALUE_PAIR in the format:
@verbatim
	<attribute_name>[:tag] <op> <value>
@endverbatim
 * to a string.
 *
 * @param out Where to write the string.
 * @param outlen Lenth of output buffer.
 * @param vp to print.
 * @return the length of data written to out, or a value >= outlen on truncation.
 */
size_t vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp)
{
	char const	*token = NULL;
	size_t		len, freespace = outlen;

	if (!out) return 0;

	*out = '\0';
	if (!vp || !vp->da) return 0;

	VERIFY_VP(vp);

	if ((vp->op > T_OP_INVALID) && (vp->op < T_TOKEN_LAST)) {
		token = vp_tokens[vp->op];
	} else {
		token = "<INVALID-TOKEN>";
	}

	if (vp->da->flags.has_tag) {
		len = snprintf(out, freespace, "%s:%d %s ", vp->da->name, vp->tag, token);
	} else {
		len = snprintf(out, freespace, "%s %s ", vp->da->name, token);
	}

	if (is_truncated(len, freespace)) return len;
	out += len;
	freespace -= len;

	len = vp_prints_value(out, freespace, vp, '\'');
	if (is_truncated(len, freespace)) return (outlen - freespace) + len;
	freespace -= len;

	return (outlen - freespace);
}


/** Print one attribute and value to FP
 *
 * Complete string with '\\t' and '\\n' is written to buffer before printing to
 * avoid issues when running with multiple threads.
 *
 * @param fp to output to.
 * @param vp to print.
 */
void vp_print(FILE *fp, VALUE_PAIR const *vp)
{
	char	buf[1024];
	char	*p = buf;
	size_t	len;

	*p++ = '\t';
	len = vp_prints(p, sizeof(buf) - 1, vp);
	if (!len) {
		return;
	}
	p += len;

	/*
	 *	Deal with truncation gracefully
	 */
	if (((size_t) (p - buf)) >= (sizeof(buf) - 2)) {
		p = buf + (sizeof(buf) - 2);
	}

	*p++ = '\n';
	*p = '\0';

	fputs(buf, fp);
}


/** Print a list of attributes and values
 *
 * @param fp to output to.
 * @param vp to print.
 */
void vp_printlist(FILE *fp, VALUE_PAIR const *vp)
{
	vp_cursor_t cursor;
	for (vp = fr_cursor_init(&cursor, &vp); vp; vp = fr_cursor_next(&cursor)) {
		vp_print(fp, vp);
	}
}

