/*-
 * Copyright (c) 2014-2018 MongoDB, Inc.
 * Copyright (c) 2008-2014 WiredTiger, Inc.
 *	All rights reserved.
 *
 * See the file LICENSE for redistribution information.
 */

/*
 * Throughout this code we have to be aware of default argument conversion.
 *
 * Refer to Chapter 8 of "Expert C Programming" by Peter van der Linden for the
 * gory details.  The short version is that we have less cases to deal with
 * because the compiler promotes shorter types to int or unsigned int.
 */
typedef struct {
	union {
		int64_t i;
		uint64_t u;
		const char *s;
		WT_ITEM item;
	} u;
	uint32_t size;
	int8_t havesize;
	char type;
} WT_PACK_VALUE;

/* Default to size = 1 if there is no size prefix. */
#define	WT_PACK_VALUE_INIT  { { 0 }, 1, 0, 0 }
#define	WT_DECL_PACK_VALUE(pv)  WT_PACK_VALUE pv = WT_PACK_VALUE_INIT

typedef struct {
	WT_SESSION_IMPL *session;
	const char *cur, *end, *orig;
	unsigned long repeats;
	WT_PACK_VALUE lastv;
} WT_PACK;

#define	WT_PACK_INIT    { NULL, NULL, NULL, NULL, 0, WT_PACK_VALUE_INIT }
#define	WT_DECL_PACK(pack)  WT_PACK pack = WT_PACK_INIT

typedef struct {
	WT_CONFIG config;
	char buf[20];
	int count;
	bool iskey;
	int genname;
} WT_PACK_NAME;

/*
 * __pack_initn --
 *      Initialize a pack iterator with the specified string and length.
 */
static inline int
__pack_initn(
    WT_SESSION_IMPL *session, WT_PACK *pack, const char *fmt, size_t len)
{
	if (*fmt == '@' || *fmt == '<' || *fmt == '>')
		return (EINVAL);
	if (*fmt == '.')
		++fmt;

	pack->session = session;
	pack->cur = pack->orig = fmt;
	pack->end = fmt + len;
	pack->repeats = 0;
	return (0);
}

/*
 * __pack_init --
 *      Initialize a pack iterator with the specified string.
 */
static inline int
__pack_init(WT_SESSION_IMPL *session, WT_PACK *pack, const char *fmt)
{
	return (__pack_initn(session, pack, fmt, strlen(fmt)));
}

/*
 * __pack_name_init --
 *      Initialize the name of a pack iterator.
 */
static inline void
__pack_name_init(WT_SESSION_IMPL *session, WT_CONFIG_ITEM *names,
    bool iskey, WT_PACK_NAME *pn)
{
	WT_CLEAR(*pn);
	pn->iskey = iskey;

	if (names->str != NULL)
		__wt_config_subinit(session, &pn->config, names);
	else
		pn->genname = 1;
}

/*
 * __pack_name_next --
 *      Get the next field type from a pack iterator.
 */
static inline int
__pack_name_next(WT_PACK_NAME *pn, WT_CONFIG_ITEM *name)
{
	WT_CONFIG_ITEM ignore;

	if (pn->genname) {
		WT_RET(__wt_snprintf(pn->buf, sizeof(pn->buf),
		    (pn->iskey ? "key%d" : "value%d"), pn->count));
		WT_CLEAR(*name);
		name->str = pn->buf;
		name->len = strlen(pn->buf);
		name->type = WT_CONFIG_ITEM_STRING;
		pn->count++;
	}
	else
		WT_RET(__wt_config_next(&pn->config, name, &ignore));

	return (0);
}

/*
 * __pack_next --
 *      Next pack iterator.
 */
static inline int
__pack_next(WT_PACK *pack, WT_PACK_VALUE *pv)
{
	char *endsize;

	if (pack->repeats > 0) {
		*pv = pack->lastv;
		--pack->repeats;
		return (0);
	}

next:	if (pack->cur == pack->end)
		return (WT_NOTFOUND);

	if (__wt_isdigit((u_char)*pack->cur)) {
		pv->havesize = 1;
		pv->size = WT_STORE_SIZE(strtoul(pack->cur, &endsize, 10));
		pack->cur = endsize;
	} else {
		pv->havesize = 0;
		pv->size = 1;
	}

	pv->type = *pack->cur++;
	pack->repeats = 0;

	switch (pv->type) {
	case 'S':
		return (0);
	case 's':
		if (pv->size < 1)
			WT_RET_MSG(pack->session, EINVAL,
			    "Fixed length strings must be at least 1 byte "
			    "in format '%.*s'",
			    (int)(pack->end - pack->orig), pack->orig);
		return (0);
	case 'x':
		return (0);
	case 't':
		if (pv->size < 1 || pv->size > 8)
			WT_RET_MSG(pack->session, EINVAL,
			    "Bitfield sizes must be between 1 and 8 bits "
			    "in format '%.*s'",
			    (int)(pack->end - pack->orig), pack->orig);
		return (0);
	case 'u':
		/* Special case for items with a size prefix. */
		pv->type = (!pv->havesize && *pack->cur != '\0') ? 'U' : 'u';
		return (0);
	case 'U':
		/*
		 * Don't change the type. 'U' is used internally, so this type
		 * was already changed to explicitly include the size.
		 */
		return (0);
	case 'b':
	case 'h':
	case 'i':
	case 'B':
	case 'H':
	case 'I':
	case 'l':
	case 'L':
	case 'q':
	case 'Q':
	case 'r':
	case 'R':
		/* Integral types repeat <size> times. */
		if (pv->size == 0)
			goto next;
		pv->havesize = 0;
		pack->repeats = pv->size - 1;
		pack->lastv = *pv;
		return (0);
	default:
		WT_RET_MSG(pack->session, EINVAL,
		    "Invalid type '%c' found in format '%.*s'",
		    pv->type, (int)(pack->end - pack->orig), pack->orig);
	}

}

#define	WT_PACK_GET(session, pv, ap) do {				\
	WT_ITEM *__item;						\
	switch ((pv).type) {						\
	case 'x':							\
		break;							\
	case 's':							\
	case 'S':							\
		(pv).u.s = va_arg(ap, const char *);			\
		break;							\
	case 'U':							\
	case 'u':							\
		__item = va_arg(ap, WT_ITEM *);				\
		(pv).u.item.data = __item->data;			\
		(pv).u.item.size = __item->size;			\
		break;							\
	case 'b':							\
	case 'h':							\
	case 'i':							\
		(pv).u.i = va_arg(ap, int);				\
		break;							\
	case 'B':							\
	case 'H':							\
	case 'I':							\
	case 't':							\
		(pv).u.u = va_arg(ap, unsigned int);			\
		break;							\
	case 'l':							\
		(pv).u.i = va_arg(ap, long);				\
		break;							\
	case 'L':							\
		(pv).u.u = va_arg(ap, unsigned long);			\
		break;							\
	case 'q':							\
		(pv).u.i = va_arg(ap, int64_t);				\
		break;							\
	case 'Q':							\
	case 'r':							\
	case 'R':							\
		(pv).u.u = va_arg(ap, uint64_t);			\
		break;							\
	/* User format strings have already been validated. */		\
	WT_ILLEGAL_VALUE(session);					\
	}								\
} while (0)

/*
 * __pack_size --
 *      Get the size of a packed value.
 */
static inline int
__pack_size(WT_SESSION_IMPL *session, WT_PACK_VALUE *pv, size_t *vp)
{
	size_t s, pad;

	switch (pv->type) {
	case 'x':
		*vp = pv->size;
		return (0);
	case 'j':
	case 'J':
	case 'K':
		/* These formats are only used internally. */
		if (pv->type == 'j' || pv->havesize)
			s = pv->size;
		else {
			ssize_t len;

			/* The string was previously validated. */
			len = __wt_json_strlen(pv->u.item.data,
			    pv->u.item.size);
			WT_ASSERT(session, len >= 0);
			s = (size_t)len + (pv->type == 'K' ? 0 : 1);
		}
		*vp = s;
		return (0);
	case 's':
	case 'S':
		if (pv->type == 's' || pv->havesize) {
			s = pv->size;
			WT_ASSERT(session, s != 0);
		} else
			s = strlen(pv->u.s) + 1;
		*vp = s;
		return (0);
	case 'U':
	case 'u':
		s = pv->u.item.size;
		pad = 0;
		if (pv->havesize && pv->size < s)
			s = pv->size;
		else if (pv->havesize)
			pad = pv->size - s;
		if (pv->type == 'U')
			s += __wt_vsize_uint(s + pad);
		*vp = s + pad;
		return (0);
	case 'b':
	case 'B':
	case 't':
		*vp = 1;
		return (0);
	case 'h':
	case 'i':
	case 'l':
	case 'q':
		*vp = __wt_vsize_int(pv->u.i);
		return (0);
	case 'H':
	case 'I':
	case 'L':
	case 'Q':
	case 'r':
		*vp = __wt_vsize_uint(pv->u.u);
		return (0);
	case 'R':
		*vp = sizeof(uint64_t);
		return (0);
	}

	WT_RET_MSG(
	    session, EINVAL, "unknown pack-value type: %c", (int)pv->type);
}

/*
 * __pack_write --
 *      Pack a value into a buffer.
 */
static inline int
__pack_write(
    WT_SESSION_IMPL *session, WT_PACK_VALUE *pv, uint8_t **pp, size_t maxlen)
{
	size_t s, pad;
	uint8_t *oldp;

	switch (pv->type) {
	case 'x':
		WT_SIZE_CHECK_PACK(pv->size, maxlen);
		memset(*pp, 0, pv->size);
		*pp += pv->size;
		break;
	case 's':
		WT_SIZE_CHECK_PACK(pv->size, maxlen);
		memcpy(*pp, pv->u.s, pv->size);
		*pp += pv->size;
		break;
	case 'S':
		/*
		 * When preceded by a size, that indicates the maximum number
		 * of bytes the string can store, this does not include the
		 * terminating NUL character. In a string with characters
		 * less than the specified size, the remaining bytes are
		 * NULL padded.
		 */
		if (pv->havesize) {
			s = __wt_strnlen(pv->u.s, pv->size);
			pad = (s < pv->size) ? pv->size - s : 0;
		} else {
			s = strlen(pv->u.s);
			pad = 1;
		}
		WT_SIZE_CHECK_PACK(s + pad, maxlen);
		if (s > 0)
			memcpy(*pp, pv->u.s, s);
		*pp += s;
		if (pad > 0) {
			memset(*pp, 0, pad);
			*pp += pad;
		}
		break;
	case 'j':
	case 'J':
	case 'K':
		/* These formats are only used internally. */
		s = pv->u.item.size;
		if ((pv->type == 'j' || pv->havesize) && pv->size < s) {
			s = pv->size;
			pad = 0;
		} else if (pv->havesize)
			pad = pv->size - s;
		else if (pv->type == 'K')
			pad = 0;
		else
			pad = 1;
		if (s > 0) {
			oldp = *pp;
			WT_RET(__wt_json_strncpy((WT_SESSION *)session,
			    (char **)pp, maxlen, pv->u.item.data, s));
			maxlen -= (size_t)(*pp - oldp);
		}
		if (pad > 0) {
			WT_SIZE_CHECK_PACK(pad, maxlen);
			memset(*pp, 0, pad);
			*pp += pad;
		}
		break;
	case 'U':
	case 'u':
		s = pv->u.item.size;
		pad = 0;
		if (pv->havesize && pv->size < s)
			s = pv->size;
		else if (pv->havesize)
			pad = pv->size - s;
		if (pv->type == 'U') {
			oldp = *pp;
			/*
			 * Check that there is at least one byte available: the
			 * low-level routines treat zero length as unchecked.
			 */
			WT_SIZE_CHECK_PACK(1, maxlen);
			WT_RET(__wt_vpack_uint(pp, maxlen, s + pad));
			maxlen -= (size_t)(*pp - oldp);
		}
		WT_SIZE_CHECK_PACK(s + pad, maxlen);
		if (s > 0)
			memcpy(*pp, pv->u.item.data, s);
		*pp += s;
		if (pad > 0) {
			memset(*pp, 0, pad);
			*pp += pad;
		}
		break;
	case 'b':
		/* Translate to maintain ordering with the sign bit. */
		WT_SIZE_CHECK_PACK(1, maxlen);
		**pp = (uint8_t)(pv->u.i + 0x80);
		*pp += 1;
		break;
	case 'B':
	case 't':
		WT_SIZE_CHECK_PACK(1, maxlen);
		**pp = (uint8_t)pv->u.u;
		*pp += 1;
		break;
	case 'h':
	case 'i':
	case 'l':
	case 'q':
		/*
		 * Check that there is at least one byte available: the
		 * low-level routines treat zero length as unchecked.
		 */
		WT_SIZE_CHECK_PACK(1, maxlen);
		WT_RET(__wt_vpack_int(pp, maxlen, pv->u.i));
		break;
	case 'H':
	case 'I':
	case 'L':
	case 'Q':
	case 'r':
		/*
		 * Check that there is at least one byte available: the
		 * low-level routines treat zero length as unchecked.
		 */
		WT_SIZE_CHECK_PACK(1, maxlen);
		WT_RET(__wt_vpack_uint(pp, maxlen, pv->u.u));
		break;
	case 'R':
		WT_SIZE_CHECK_PACK(sizeof(uint64_t), maxlen);
		*(uint64_t *)*pp = pv->u.u;
		*pp += sizeof(uint64_t);
		break;
	default:
		WT_RET_MSG(session, EINVAL,
		    "unknown pack-value type: %c", (int)pv->type);
	}

	return (0);
}

/*
 * __unpack_read --
 *      Read a packed value from a buffer.
 */
static inline int
__unpack_read(WT_SESSION_IMPL *session,
    WT_PACK_VALUE *pv, const uint8_t **pp, size_t maxlen)
{
	size_t s;

	switch (pv->type) {
	case 'x':
		WT_SIZE_CHECK_UNPACK(pv->size, maxlen);
		*pp += pv->size;
		break;
	case 's':
	case 'S':
		if (pv->type == 's' || pv->havesize) {
			s = pv->size;
			WT_ASSERT(session, s != 0);
		} else
			s = strlen((const char *)*pp) + 1;
		if (s > 0)
			pv->u.s = (const char *)*pp;
		WT_SIZE_CHECK_UNPACK(s, maxlen);
		*pp += s;
		break;
	case 'U':
		/*
		 * Check that there is at least one byte available: the
		 * low-level routines treat zero length as unchecked.
		 */
		WT_SIZE_CHECK_UNPACK(1, maxlen);
		WT_RET(__wt_vunpack_uint(pp, maxlen, &pv->u.u));
		/* FALLTHROUGH */
	case 'u':
		if (pv->havesize)
			s = pv->size;
		else if (pv->type == 'U')
			s = (size_t)pv->u.u;
		else
			s = maxlen;
		WT_SIZE_CHECK_UNPACK(s, maxlen);
		pv->u.item.data = *pp;
		pv->u.item.size = s;
		*pp += s;
		break;
	case 'b':
		/* Translate to maintain ordering with the sign bit. */
		WT_SIZE_CHECK_UNPACK(1, maxlen);
		pv->u.i = (int8_t)(*(*pp)++ - 0x80);
		break;
	case 'B':
	case 't':
		WT_SIZE_CHECK_UNPACK(1, maxlen);
		pv->u.u = *(*pp)++;
		break;
	case 'h':
	case 'i':
	case 'l':
	case 'q':
		/*
		 * Check that there is at least one byte available: the
		 * low-level routines treat zero length as unchecked.
		 */
		WT_SIZE_CHECK_UNPACK(1, maxlen);
		WT_RET(__wt_vunpack_int(pp, maxlen, &pv->u.i));
		break;
	case 'H':
	case 'I':
	case 'L':
	case 'Q':
	case 'r':
		/*
		 * Check that there is at least one byte available: the
		 * low-level routines treat zero length as unchecked.
		 */
		WT_SIZE_CHECK_UNPACK(1, maxlen);
		WT_RET(__wt_vunpack_uint(pp, maxlen, &pv->u.u));
		break;
	case 'R':
		WT_SIZE_CHECK_UNPACK(sizeof(uint64_t), maxlen);
		pv->u.u = *(const uint64_t *)*pp;
		*pp += sizeof(uint64_t);
		break;
	default:
		WT_RET_MSG(session, EINVAL,
		    "unknown pack-value type: %c", (int)pv->type);
	}

	return (0);
}

#define	WT_UNPACK_PUT(session, pv, ap) do {				\
	WT_ITEM *__item;						\
	switch ((pv).type) {						\
	case 'x':							\
		break;							\
	case 's':							\
	case 'S':							\
		*va_arg(ap, const char **) = (pv).u.s;			\
		break;							\
	case 'U':							\
	case 'u':							\
		__item = va_arg(ap, WT_ITEM *);				\
		__item->data = (pv).u.item.data;			\
		__item->size = (pv).u.item.size;			\
		break;							\
	case 'b':							\
		*va_arg(ap, int8_t *) = (int8_t)(pv).u.i;		\
		break;							\
	case 'h':							\
		*va_arg(ap, int16_t *) = (short)(pv).u.i;		\
		break;							\
	case 'i':							\
	case 'l':							\
		*va_arg(ap, int32_t *) = (int32_t)(pv).u.i;		\
		break;							\
	case 'q':							\
		*va_arg(ap, int64_t *) = (pv).u.i;			\
		break;							\
	case 'B':							\
	case 't':							\
		*va_arg(ap, uint8_t *) = (uint8_t)(pv).u.u;		\
		break;							\
	case 'H':							\
		*va_arg(ap, uint16_t *) = (uint16_t)(pv).u.u;		\
		break;							\
	case 'I':							\
	case 'L':							\
		*va_arg(ap, uint32_t *) = (uint32_t)(pv).u.u;		\
		break;							\
	case 'Q':							\
	case 'r':							\
	case 'R':							\
		*va_arg(ap, uint64_t *) = (pv).u.u;			\
		break;							\
	/* User format strings have already been validated. */		\
	WT_ILLEGAL_VALUE(session);					\
	}								\
} while (0)

/*
 * __wt_struct_packv --
 *	Pack a byte string (va_list version).
 */
static inline int
__wt_struct_packv(WT_SESSION_IMPL *session,
    void *buffer, size_t size, const char *fmt, va_list ap)
{
	WT_DECL_PACK_VALUE(pv);
	WT_DECL_RET;
	WT_PACK pack;
	uint8_t *p, *end;

	p = buffer;
	end = p + size;

	if (fmt[0] != '\0' && fmt[1] == '\0') {
		pv.type = fmt[0];
		WT_PACK_GET(session, pv, ap);
		return (__pack_write(session, &pv, &p, size));
	}

	WT_RET(__pack_init(session, &pack, fmt));
	while ((ret = __pack_next(&pack, &pv)) == 0) {
		WT_PACK_GET(session, pv, ap);
		WT_RET(__pack_write(session, &pv, &p, (size_t)(end - p)));
	}
	WT_RET_NOTFOUND_OK(ret);

	/* Be paranoid - __pack_write should never overflow. */
	WT_ASSERT(session, p <= end);

	return (0);
}

/*
 * __wt_struct_sizev --
 *	Calculate the size of a packed byte string (va_list version).
 */
static inline int
__wt_struct_sizev(
    WT_SESSION_IMPL *session, size_t *sizep, const char *fmt, va_list ap)
{
	WT_DECL_PACK_VALUE(pv);
	WT_DECL_RET;
	WT_PACK pack;
	size_t v;

	*sizep = 0;

	if (fmt[0] != '\0' && fmt[1] == '\0') {
		pv.type = fmt[0];
		WT_PACK_GET(session, pv, ap);
		return (__pack_size(session, &pv, sizep));
	}

	WT_RET(__pack_init(session, &pack, fmt));
	while ((ret = __pack_next(&pack, &pv)) == 0) {
		WT_PACK_GET(session, pv, ap);
		WT_RET(__pack_size(session, &pv, &v));
		*sizep += v;
	}
	WT_RET_NOTFOUND_OK(ret);

	return (0);
}

/*
 * __wt_struct_unpackv --
 *	Unpack a byte string (va_list version).
 */
static inline int
__wt_struct_unpackv(WT_SESSION_IMPL *session,
    const void *buffer, size_t size, const char *fmt, va_list ap)
{
	WT_DECL_PACK_VALUE(pv);
	WT_DECL_RET;
	WT_PACK pack;
	const uint8_t *p, *end;

	p = buffer;
	end = p + size;

	if (fmt[0] != '\0' && fmt[1] == '\0') {
		pv.type = fmt[0];
		WT_RET(__unpack_read(session, &pv, &p, size));
		WT_UNPACK_PUT(session, pv, ap);
		return (0);
	}

	WT_RET(__pack_init(session, &pack, fmt));
	while ((ret = __pack_next(&pack, &pv)) == 0) {
		WT_RET(__unpack_read(session, &pv, &p, (size_t)(end - p)));
		WT_UNPACK_PUT(session, pv, ap);
	}
	WT_RET_NOTFOUND_OK(ret);

	/* Be paranoid - __pack_write should never overflow. */
	WT_ASSERT(session, p <= end);

	return (0);
}

/*
 * __wt_struct_size_adjust --
 *	Adjust the size field for a packed structure.
 *
 *      Sometimes we want to include the size as a field in a packed structure.
 *      This is done by calling __wt_struct_size with the expected format and
 *      a size of zero.  Then we want to pack the structure using the final
 *      size.  This function adjusts the size appropriately (taking into
 *      account the size of the final size or the size field itself).
 */
static inline void
__wt_struct_size_adjust(WT_SESSION_IMPL *session, size_t *sizep)
{
	size_t curr_size, field_size, prev_field_size;

	curr_size = *sizep;
	prev_field_size = 1;

	while ((field_size = __wt_vsize_uint(curr_size)) != prev_field_size) {
		curr_size += field_size - prev_field_size;
		prev_field_size = field_size;
	}

	/* Make sure the field size we calculated matches the adjusted size. */
	WT_ASSERT(session, field_size == __wt_vsize_uint(curr_size));

	*sizep = curr_size;
}
