/*
 *  CBOR bindings.
 *
 *  http://cbor.io/
 *  https://tools.ietf.org/html/rfc7049
 */

#include "duk_internal.h"

#if defined(DUK_USE_CBOR_SUPPORT)

/* #define DUK_CBOR_STRESS */

/* Default behavior for encoding strings: use CBOR text string if string
 * is UTF-8 compatible, otherwise use CBOR byte string.  These defines
 * can be used to force either type for all strings.  Using text strings
 * for non-UTF-8 data is technically invalid CBOR.
 */
/* #define DUK_CBOR_TEXT_STRINGS */
/* #define DUK_CBOR_BYTE_STRINGS */

/* Misc. defines. */
/* #define DUK_CBOR_PREFER_SIZE */
/* #define DUK_CBOR_DOUBLE_AS_IS */
/* #define DUK_CBOR_DECODE_FASTPATH */

typedef struct {
	duk_hthread *thr;
	duk_uint8_t *ptr;
	duk_uint8_t *buf;
	duk_uint8_t *buf_end;
	duk_size_t len;
	duk_idx_t idx_buf;
} duk_cbor_encode_context;

typedef struct {
	duk_hthread *thr;
	const duk_uint8_t *buf;
	duk_size_t off;
	duk_size_t len;
} duk_cbor_decode_context;

DUK_LOCAL void duk__cbor_encode_value(duk_cbor_encode_context *enc_ctx);
DUK_LOCAL void duk__cbor_decode_value(duk_cbor_decode_context *dec_ctx);

/*
 *  Misc
 */

DUK_LOCAL duk_uint32_t duk__cbor_double_to_uint32(double d) {
	/* Out of range casts are undefined behavior, so caller must avoid. */
	DUK_ASSERT(d >= 0.0 && d <= 4294967295.0);
	return (duk_uint32_t) d;
}

/*
 *  Encoding
 */

DUK_LOCAL void duk__cbor_encode_error(duk_cbor_encode_context *enc_ctx) {
	(void) duk_type_error(enc_ctx->thr, "cbor encode error");
}

/* Check that a size_t is in uint32 range to avoid out-of-range casts. */
DUK_LOCAL void duk__cbor_encode_sizet_uint32_check(duk_cbor_encode_context *enc_ctx, duk_size_t len) {
	if (DUK_UNLIKELY(sizeof(duk_size_t) > sizeof(duk_uint32_t) && len > (duk_size_t) DUK_UINT32_MAX)) {
		duk__cbor_encode_error(enc_ctx);
	}
}

DUK_LOCAL DUK_NOINLINE void duk__cbor_encode_ensure_slowpath(duk_cbor_encode_context *enc_ctx, duk_size_t len) {
	duk_size_t oldlen;
	duk_size_t minlen;
	duk_size_t newlen;
	duk_uint8_t *p_new;
	duk_size_t old_data_len;

	DUK_ASSERT(enc_ctx->ptr >= enc_ctx->buf);
	DUK_ASSERT(enc_ctx->buf_end >= enc_ctx->ptr);
	DUK_ASSERT(enc_ctx->buf_end >= enc_ctx->buf);

	/* Overflow check.
	 *
	 * Limit example: 0xffffffffUL / 2U = 0x7fffffffUL, we reject >= 0x80000000UL.
	 */
	oldlen = enc_ctx->len;
	minlen = oldlen + len;
	if (DUK_UNLIKELY(oldlen > DUK_SIZE_MAX / 2U || minlen < oldlen)) {
		duk__cbor_encode_error(enc_ctx);
	}

#if defined(DUK_CBOR_STRESS)
	newlen = oldlen + 1U;
#else
	newlen = oldlen * 2U;
#endif
	DUK_ASSERT(newlen >= oldlen);

	if (minlen > newlen) {
		newlen = minlen;
	}
	DUK_ASSERT(newlen >= oldlen);
	DUK_ASSERT(newlen >= minlen);
	DUK_ASSERT(newlen > 0U);

	DUK_DD(DUK_DDPRINT("cbor encode buffer resized to %ld", (long) newlen));

	p_new = (duk_uint8_t *) duk_resize_buffer(enc_ctx->thr, enc_ctx->idx_buf, newlen);
	DUK_ASSERT(p_new != NULL);
	old_data_len = (duk_size_t) (enc_ctx->ptr - enc_ctx->buf);
	enc_ctx->buf = p_new;
	enc_ctx->buf_end = p_new + newlen;
	enc_ctx->ptr = p_new + old_data_len;
	enc_ctx->len = newlen;
}

DUK_LOCAL DUK_INLINE void duk__cbor_encode_ensure(duk_cbor_encode_context *enc_ctx, duk_size_t len) {
	if (DUK_LIKELY((duk_size_t) (enc_ctx->buf_end - enc_ctx->ptr) >= len)) {
		return;
	}
	duk__cbor_encode_ensure_slowpath(enc_ctx, len);
}

DUK_LOCAL duk_size_t duk__cbor_get_reserve(duk_cbor_encode_context *enc_ctx) {
	DUK_ASSERT(enc_ctx->ptr >= enc_ctx->buf);
	DUK_ASSERT(enc_ctx->ptr <= enc_ctx->buf_end);
	return (duk_size_t) (enc_ctx->buf_end - enc_ctx->ptr);
}

DUK_LOCAL void duk__cbor_encode_uint32(duk_cbor_encode_context *enc_ctx, duk_uint32_t u, duk_uint8_t base) {
	duk_uint8_t *p;

	/* Caller must ensure space. */
	DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 4);

	p = enc_ctx->ptr;
	if (DUK_LIKELY(u <= 23U)) {
		*p++ = (duk_uint8_t) (base + (duk_uint8_t) u);
	} else if (u <= 0xffUL) {
		*p++ = base + 0x18U;
		*p++ = (duk_uint8_t) u;
	} else if (u <= 0xffffUL) {
		*p++ = base + 0x19U;
		DUK_RAW_WRITEINC_U16_BE(p, (duk_uint16_t) u);
	} else {
		*p++ = base + 0x1aU;
		DUK_RAW_WRITEINC_U32_BE(p, u);
	}
	enc_ctx->ptr = p;
}

#if defined(DUK_CBOR_DOUBLE_AS_IS)
DUK_LOCAL void duk__cbor_encode_double(duk_cbor_encode_context *enc_ctx, double d) {
	duk_uint8_t *p;

	/* Caller must ensure space. */
	DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8);

	p = enc_ctx->ptr;
	*p++ = 0xfbU;
	DUK_RAW_WRITEINC_DOUBLE_BE(p, d);
	p += 8;
	enc_ctx->ptr = p;
}
#else  /* DUK_CBOR_DOUBLE_AS_IS */
DUK_LOCAL void duk__cbor_encode_double_fp(duk_cbor_encode_context *enc_ctx, double d) {
	duk_double_union u;
	duk_uint16_t u16;
	duk_int16_t expt;
	duk_uint8_t *p;

	DUK_ASSERT(DUK_FPCLASSIFY(d) != DUK_FP_ZERO);

	/* Caller must ensure space. */
	DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8);

	/* Organize into little endian (no-op if platform is little endian). */
	u.d = d;
	duk_dblunion_host_to_little(&u);

	/* Check if 'd' can represented as a normal half-float.
	 * Denormal half-floats could also be used, but that check
	 * isn't done now (denormal half-floats are decoded of course).
	 * So just check exponent range and that at most 10 significant
	 * bits (excluding implicit leading 1) are used in 'd'.
	 */
	u16 = (((duk_uint16_t) u.uc[7]) << 8) | ((duk_uint16_t) u.uc[6]);
	expt = (duk_int16_t) ((u16 & 0x7ff0U) >> 4) - 1023;

	if (expt >= -14 && expt <= 15) {
		/* Half-float normal exponents (excl. denormals).
		 *
		 *          7        6        5        4        3        2        1        0  (LE index)
		 * double: seeeeeee eeeemmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm
		 * half:         seeeee mmmm mmmmmm00 00000000 00000000 00000000 00000000 00000000
		 */
		duk_bool_t use_half_float;

		use_half_float =
		    (u.uc[0] == 0 && u.uc[1] == 0 && u.uc[2] == 0 && u.uc[3] == 0 &&
		     u.uc[4] == 0 && (u.uc[5] & 0x03U) == 0);

		if (use_half_float) {
			duk_uint32_t t;

			expt += 15;
			t = (duk_uint32_t) (u.uc[7] & 0x80U) << 8;
			t += (duk_uint32_t) expt << 10;
			t += ((duk_uint32_t) u.uc[6] & 0x0fU) << 6;
			t += ((duk_uint32_t) u.uc[5]) >> 2;

			/* seeeeemm mmmmmmmm */
			p = enc_ctx->ptr;
			*p++ = 0xf9U;
			DUK_RAW_WRITEINC_U16_BE(p, (duk_uint16_t) t);
			enc_ctx->ptr = p;
			return;
		}
	}

	/* Same check for plain float.  Also no denormal support here. */
	if (expt >= -126 && expt <= 127) {
		/* Float normal exponents (excl. denormals).
		 *
		 * double: seeeeeee eeeemmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm mmmmmmmm
		 * float:     seeee eeeemmmm mmmmmmmm mmmmmmmm mmm00000 00000000 00000000 00000000
		 */
		duk_bool_t use_float;
		duk_float_t d_float;

		/* We could do this explicit mantissa check, but doing
		 * a double-float-double cast is fine because we've
		 * already verified that the exponent is in range so
		 * that the narrower cast is not undefined behavior.
		 */
#if 0
		use_float =
		    (u.uc[0] == 0 && u.uc[1] == 0 && u.uc[2] == 0 && (u.uc[3] & 0xe0U) == 0);
#endif
		d_float = (duk_float_t) d;
		use_float = duk_double_equals((duk_double_t) d_float, d);
		if (use_float) {
			p = enc_ctx->ptr;
			*p++ = 0xfaU;
			DUK_RAW_WRITEINC_FLOAT_BE(p, d_float);
			enc_ctx->ptr = p;
			return;
		}
	}

	/* Special handling for NaN and Inf which we want to encode as
	 * half-floats.  They share the same (maximum) exponent.
	 */
	if (expt == 1024) {
		DUK_ASSERT(DUK_ISNAN(d) || DUK_ISINF(d));
		p = enc_ctx->ptr;
		*p++ = 0xf9U;
		if (DUK_ISNAN(d)) {
			/* Shortest NaN encoding is using a half-float.  Lose the
			 * exact NaN bits in the process.  IEEE double would be
			 * 7ff8 0000 0000 0000, i.e. a quiet NaN in most architectures
			 * (https://en.wikipedia.org/wiki/NaN#Encoding).  The
			 * equivalent half float is 7e00.
			 */
			*p++ = 0x7eU;
		} else {
			/* Shortest +/- Infinity encoding is using a half-float. */
			if (DUK_SIGNBIT(d)) {
				*p++ = 0xfcU;
			} else {
				*p++ = 0x7cU;
			}
		}
		*p++ = 0x00U;
		enc_ctx->ptr = p;
		return;
	}

	/* Cannot use half-float or float, encode as full IEEE double. */
	p = enc_ctx->ptr;
	*p++ = 0xfbU;
	DUK_RAW_WRITEINC_DOUBLE_BE(p, d);
	enc_ctx->ptr = p;
}

DUK_LOCAL void duk__cbor_encode_double(duk_cbor_encode_context *enc_ctx, double d) {
	duk_uint8_t *p;
	double d_floor;

	/* Integers and floating point values of all types are conceptually
	 * equivalent in CBOR.  Try to always choose the shortest encoding
	 * which is not always immediately obvious.  For example, NaN and Inf
	 * can be most compactly represented as a half-float (assuming NaN
	 * bits are not preserved), and 0x1'0000'0000 as a single precision
	 * float.  Shortest forms in preference order (prefer integer over
	 * float when equal length):
	 *
	 *   uint        1 byte    [0,23] (not -0)
	 *   sint        1 byte    [-24,-1]
	 *   uint+1      2 bytes   [24,255]
	 *   sint+1      2 bytes   [-256,-25]
	 *   uint+2      3 bytes   [256,65535]
	 *   sint+2      3 bytes   [-65536,-257]
	 *   half-float  3 bytes   -0, NaN, +/- Infinity, range [-65504,65504]
	 *   uint+4      5 bytes   [65536,4294967295]
	 *   sint+4      5 bytes   [-4294967296,-258]
	 *   float       5 bytes   range [-(1 - 2^(-24)) * 2^128, (1 - 2^(-24)) * 2^128]
	 *   uint+8      9 bytes   [4294967296,18446744073709551615]
	 *   sint+8      9 bytes   [-18446744073709551616,-4294967297]
	 *   double      9 bytes
	 *
	 * For whole numbers (compatible with integers):
	 *   - 1-byte or 2-byte uint/sint representation is preferred for
	 *     [-256,255].
	 *   - 3-byte uint/sint is preferred for [-65536,65535].  Half floats
	 *     are never preferred because they have the same length.
	 *   - 5-byte uint/sint is preferred for [-4294967296,4294967295].
	 *     Single precision floats are never preferred, and half-floats
	 *     don't reach above the 3-byte uint/sint range so they're never
	 *     preferred.
	 *   - So, for all integers up to signed/unsigned 32-bit range the
	 *     preferred encoding is always an integer uint/sint.
	 *   - For integers above 32 bits the situation is more complicated.
	 *     Half-floats are never useful for them because of their limited
	 *     range, but IEEE single precision floats (5 bytes encoded) can
	 *     represent some integers between the 32-bit and 64-bit ranges
	 *     which require 9 bytes as a uint/sint.
	 *
	 * For floating point values not compatible with integers, the
	 * preferred encoding is quite clear:
	 *   - For +Inf/-Inf use half-float.
	 *   - For NaN use a half-float, assuming NaN bits ("payload") is
	 *     not worth preserving.  Duktape doesn't in general guarantee
	 *     preservation of the NaN payload so using a half-float seems
	 *     consistent with that.
	 *   - For remaining values, prefer the shortest form which doesn't
	 *     lose any precision.  For normal half-floats and single precision
	 *     floats this is simple: just check exponent and mantissa bits
	 *     using a fixed mask.  For denormal half-floats and single
	 *     precision floats the check is a bit more complicated: a normal
	 *     IEEE double can sometimes be represented as a denormal
	 *     half-float or single precision float.
	 *
	 * https://en.wikipedia.org/wiki/Half-precision_floating-point_format#IEEE_754_half-precision_binary_floating-point_format:_binary16
	 */

	/* Caller must ensure space. */
	DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8);

	/* Most important path is integers.  The floor() test will be true
	 * for Inf too (but not NaN).
	 */
	d_floor = DUK_FLOOR(d);  /* identity if d is +/- 0.0, NaN, or +/- Infinity */
	if (DUK_LIKELY(duk_double_equals(d_floor, d) != 0)) {
		DUK_ASSERT(!DUK_ISNAN(d));  /* NaN == NaN compares false. */
		if (DUK_SIGNBIT(d)) {
			if (d >= -4294967296.0) {
				d = -1.0 - d;
				if (d >= 0.0) {
					DUK_ASSERT(d >= 0.0);
					duk__cbor_encode_uint32(enc_ctx, duk__cbor_double_to_uint32(d), 0x20U);
					return;
				}

				/* Input was negative zero, d == -1.0 < 0.0.
				 * Shortest -0 is using half-float.
				 */
				p = enc_ctx->ptr;
				*p++ = 0xf9U;
				*p++ = 0x80U;
				*p++ = 0x00U;
				enc_ctx->ptr = p;
				return;
			}
		} else {
			if (d <= 4294967295.0) {
				/* Positive zero needs no special handling. */
				DUK_ASSERT(d >= 0.0);
				duk__cbor_encode_uint32(enc_ctx, duk__cbor_double_to_uint32(d), 0x00U);
				return;
			}
		}
	}

	/* 64-bit integers are not supported at present.  So
	 * we also don't need to deal with choosing between a
	 * 64-bit uint/sint representation vs. IEEE double or
	 * float.
	 */

	DUK_ASSERT(DUK_FPCLASSIFY(d) != DUK_FP_ZERO);
	duk__cbor_encode_double_fp(enc_ctx, d);
}
#endif  /* DUK_CBOR_DOUBLE_AS_IS */

DUK_LOCAL void duk__cbor_encode_string_top(duk_cbor_encode_context *enc_ctx) {
	const duk_uint8_t *str;
	duk_size_t len;
	duk_uint8_t *p;

	/* CBOR differentiates between UTF-8 text strings and byte strings.
	 * Text strings MUST be valid UTF-8, so not all Duktape strings can
	 * be encoded as valid CBOR text strings.  Possible behaviors:
	 *
	 *   1. Use text string when input is valid UTF-8, otherwise use
	 *      byte string (maybe tagged to indicate it was an extended
	 *      UTF-8 string).
	 *   2. Always use text strings, but sanitize input string so that
	 *      invalid UTF-8 is replaced with U+FFFD for example.  Combine
	 *      surrogates whenever possible.
	 *   3. Always use byte strings.  This is simple and produces valid
	 *      CBOR, but isn't ideal for interoperability.
	 *   4. Always use text strings, even for invalid UTF-8 such as
	 *      codepoints in the surrogate pair range.  This is simple but
	 *      produces technically invalid CBOR for non-UTF-8 strings which
	 *      may affect interoperability.
	 *
	 * Current default is 1; can be changed with defines.
	 */

	/* Caller must ensure space. */
	DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8);

	str = (const duk_uint8_t *) duk_require_lstring(enc_ctx->thr, -1, &len);
	if (duk_is_symbol(enc_ctx->thr, -1)) {
		/* Symbols, encode as an empty table for now.  This matches
		 * the behavior of cbor-js.
		 *
		 * XXX: Maybe encode String() coercion with a tag?
		 * XXX: Option to keep enough information to recover
		 * Symbols when decoding (this is not always desirable).
		 */
		p = enc_ctx->ptr;
		*p++ = 0xa0U;
		enc_ctx->ptr = p;
		return;
	}

	duk__cbor_encode_sizet_uint32_check(enc_ctx, len);
#if defined(DUK_CBOR_TEXT_STRINGS)
	duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x60U);
#elif defined(DUK_CBOR_BYTE_STRINGS)
	duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x40U);
#else
	duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len,
	                        (DUK_LIKELY(duk_unicode_is_utf8_compatible(str, len) != 0) ? 0x60U : 0x40U));
#endif
	duk__cbor_encode_ensure(enc_ctx, len);
	p = enc_ctx->ptr;
	duk_memcpy((void *) p, (const void *) str, len);
	p += len;
	enc_ctx->ptr = p;
}

DUK_LOCAL void duk__cbor_encode_object(duk_cbor_encode_context *enc_ctx) {
	duk_uint8_t *buf;
	duk_size_t len;
	duk_uint8_t *p;
	duk_size_t i;
	duk_size_t off_ib;
	duk_uint32_t count;

	/* Caller must ensure space. */
	DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8);

	/* XXX: Support for specific built-ins like Date and RegExp. */
	if (duk_is_array(enc_ctx->thr, -1)) {
		/* Shortest encoding for arrays >= 256 in length is actually
		 * the indefinite length one (3 or more bytes vs. 2 bytes).
		 * We still use the definite length version because it is
		 * more decoding friendly.
		 */
		len = duk_get_length(enc_ctx->thr, -1);
		duk__cbor_encode_sizet_uint32_check(enc_ctx, len);
		duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x80U);
		for (i = 0; i < len; i++) {
			duk_get_prop_index(enc_ctx->thr, -1, (duk_uarridx_t) i);
			duk__cbor_encode_value(enc_ctx);
		}
	} else if (duk_is_buffer_data(enc_ctx->thr, -1)) {
		/* XXX: Tag buffer data?
		 * XXX: Encode typed arrays as integer arrays rather
		 * than buffer data as is?
		 */
		buf = (duk_uint8_t *) duk_require_buffer_data(enc_ctx->thr, -1, &len);
		duk__cbor_encode_sizet_uint32_check(enc_ctx, len);
		duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x40U);
		duk__cbor_encode_ensure(enc_ctx, len);
		p = enc_ctx->ptr;
		duk_memcpy((void *) p, (const void *) buf, len);
		p += len;
		enc_ctx->ptr = p;
	} else {
		/* We don't know the number of properties in advance
		 * but would still like to encode at least small
		 * objects without indefinite length.  Emit an
		 * indefinite length byte initially, and if the final
		 * property count is small enough to also fit in one
		 * byte, backpatch it later.  Otherwise keep the
		 * indefinite length.  This works well up to 23
		 * properties which is practical and good enough.
		 */
		off_ib = (duk_size_t) (enc_ctx->ptr - enc_ctx->buf);  /* XXX: get_offset? */
		count = 0U;
		p = enc_ctx->ptr;
		*p++ = 0xa0U + 0x1fU;  /* indefinite length */
		enc_ctx->ptr = p;
		duk_enum(enc_ctx->thr, -1, DUK_ENUM_OWN_PROPERTIES_ONLY);
		while (duk_next(enc_ctx->thr, -1, 1 /*get_value*/)) {
			duk_insert(enc_ctx->thr, -2);  /* [ ... key value ] -> [ ... value key ] */
			duk__cbor_encode_value(enc_ctx);
			duk__cbor_encode_value(enc_ctx);
			count++;
			if (count == 0U) {
				duk__cbor_encode_error(enc_ctx);
			}
		}
		duk_pop(enc_ctx->thr);
		if (count <= 0x17U) {
			DUK_ASSERT(off_ib < enc_ctx->len);
			enc_ctx->buf[off_ib] = 0xa0U + (duk_uint8_t) count;
		} else {
			duk__cbor_encode_ensure(enc_ctx, 1);
			p = enc_ctx->ptr;
			*p++ = 0xffU;  /* break */
			enc_ctx->ptr = p;
		}
	}
}

DUK_LOCAL void duk__cbor_encode_buffer(duk_cbor_encode_context *enc_ctx) {
	duk_uint8_t *buf;
	duk_size_t len;
	duk_uint8_t *p;

	/* Caller must ensure space. */
	DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8);

	/* Tag buffer data? */
	buf = (duk_uint8_t *) duk_require_buffer(enc_ctx->thr, -1, &len);
	duk__cbor_encode_sizet_uint32_check(enc_ctx, len);
	duk__cbor_encode_uint32(enc_ctx, (duk_uint32_t) len, 0x40U);
	duk__cbor_encode_ensure(enc_ctx, len);
	p = enc_ctx->ptr;
	duk_memcpy((void *) p, (const void *) buf, len);
	p += len;
	enc_ctx->ptr = p;
}

DUK_LOCAL void duk__cbor_encode_pointer(duk_cbor_encode_context *enc_ctx) {
	/* Pointers (void *) are challenging to encode.  They can't
	 * be relied to be even 64-bit integer compatible (there are
	 * pointer models larger than that), nor can floats encode
	 * them.  They could be encoded as strings (%p format) but
	 * that's not portable.  They could be encoded as direct memory
	 * representations.  Recovering pointers is non-portable in any
	 * case but it would be nice to be able to detect and recover
	 * compatible pointers.
	 *
	 * For now, encode as "(%p)" string, matching JX.  There doesn't
	 * seem to be an appropriate tag, so pointers don't currently
	 * survive a CBOR encode/decode roundtrip intact.
	 */
	const char *ptr;

	ptr = duk_to_string(enc_ctx->thr, -1);
	DUK_ASSERT(ptr != NULL);
	duk_push_sprintf(enc_ctx->thr, "(%s)", ptr);
	duk_remove(enc_ctx->thr, -2);
	duk__cbor_encode_string_top(enc_ctx);
}

DUK_LOCAL void duk__cbor_encode_lightfunc(duk_cbor_encode_context *enc_ctx) {
	duk_uint8_t *p;

	/* Caller must ensure space. */
	DUK_ASSERT(duk__cbor_get_reserve(enc_ctx) >= 1 + 8);

	/* For now encode as an empty object. */
	p = enc_ctx->ptr;
	*p++ = 0xa0U;
	enc_ctx->ptr = p;
}

DUK_LOCAL void duk__cbor_encode_value(duk_cbor_encode_context *enc_ctx) {
	duk_uint8_t *p;

	/* Encode/decode cycle currently loses some type information.
	 * This can be improved by registering custom tags with IANA.
	 */

	/* When working with deeply recursive structures, this is important
	 * to ensure there's no effective depth limit.
	 */
	duk_require_stack(enc_ctx->thr, 4);

	/* Reserve space for up to 64-bit types (1 initial byte + 8
	 * followup bytes).  This allows encoding of integers, floats,
	 * string/buffer length fields, etc without separate checks
	 * in each code path.
	 */
	duk__cbor_encode_ensure(enc_ctx, 1 + 8);

	switch (duk_get_type(enc_ctx->thr, -1)) {
	case DUK_TYPE_UNDEFINED: {
		p = enc_ctx->ptr;
		*p++ = 0xf7;
		enc_ctx->ptr = p;
		break;
	}
	case DUK_TYPE_NULL: {
		p = enc_ctx->ptr;
		*p++ = 0xf6;
		enc_ctx->ptr = p;
		break;
	}
	case DUK_TYPE_BOOLEAN: {
		duk_uint8_t u8 = duk_get_boolean(enc_ctx->thr, -1) ? 0xf5U : 0xf4U;
		p = enc_ctx->ptr;
		*p++ = u8;
		enc_ctx->ptr = p;
		break;
	}
	case DUK_TYPE_NUMBER: {
		duk__cbor_encode_double(enc_ctx, duk_get_number(enc_ctx->thr, -1));
		break;
	}
	case DUK_TYPE_STRING: {
		duk__cbor_encode_string_top(enc_ctx);
		break;
	}
	case DUK_TYPE_OBJECT: {
		duk__cbor_encode_object(enc_ctx);
		break;
	}
	case DUK_TYPE_BUFFER: {
		duk__cbor_encode_buffer(enc_ctx);
		break;
	}
	case DUK_TYPE_POINTER: {
		duk__cbor_encode_pointer(enc_ctx);
		break;
	}
	case DUK_TYPE_LIGHTFUNC: {
		duk__cbor_encode_lightfunc(enc_ctx);
		break;
	}
	case DUK_TYPE_NONE:
	default:
		goto fail;
	}

	duk_pop(enc_ctx->thr);
	return;

 fail:
	duk__cbor_encode_error(enc_ctx);
}

/*
 *  Decoding
 */

DUK_LOCAL void duk__cbor_req_stack(duk_cbor_decode_context *dec_ctx) {
	duk_require_stack(dec_ctx->thr, 4);
}

DUK_LOCAL void duk__cbor_decode_error(duk_cbor_decode_context *dec_ctx) {
	(void) duk_type_error(dec_ctx->thr, "cbor decode error");
}

DUK_LOCAL duk_uint8_t duk__cbor_decode_readbyte(duk_cbor_decode_context *dec_ctx) {
	DUK_ASSERT(dec_ctx->off <= dec_ctx->len);
	if (DUK_UNLIKELY(dec_ctx->len - dec_ctx->off < 1U)) {
		duk__cbor_decode_error(dec_ctx);
	}
	return dec_ctx->buf[dec_ctx->off++];
}

DUK_LOCAL duk_uint16_t duk__cbor_decode_read_u16(duk_cbor_decode_context *dec_ctx) {
	duk_uint16_t res;

	DUK_ASSERT(dec_ctx->off <= dec_ctx->len);
	if (DUK_UNLIKELY(dec_ctx->len - dec_ctx->off < 2U)) {
		duk__cbor_decode_error(dec_ctx);
	}
	res = DUK_RAW_READ_U16_BE(dec_ctx->buf + dec_ctx->off);
	dec_ctx->off += 2;
	return res;
}

DUK_LOCAL duk_uint32_t duk__cbor_decode_read_u32(duk_cbor_decode_context *dec_ctx) {
	duk_uint32_t res;

	DUK_ASSERT(dec_ctx->off <= dec_ctx->len);
	if (DUK_UNLIKELY(dec_ctx->len - dec_ctx->off < 4U)) {
		duk__cbor_decode_error(dec_ctx);
	}
	res = DUK_RAW_READ_U32_BE(dec_ctx->buf + dec_ctx->off);
	dec_ctx->off += 4;
	return res;
}

DUK_LOCAL duk_uint8_t duk__cbor_decode_peekbyte(duk_cbor_decode_context *dec_ctx) {
	if (DUK_UNLIKELY(dec_ctx->off >= dec_ctx->len)) {
		duk__cbor_decode_error(dec_ctx);
	}
	return dec_ctx->buf[dec_ctx->off];
}

DUK_LOCAL void duk__cbor_decode_rewind(duk_cbor_decode_context *dec_ctx, duk_size_t len) {
	DUK_ASSERT(len <= dec_ctx->off);  /* Caller must ensure. */
	dec_ctx->off -= len;
}

#if 0
DUK_LOCAL void duk__cbor_decode_ensure(duk_cbor_decode_context *dec_ctx, duk_size_t len) {
	if (dec_ctx->off + len > dec_ctx->len) {
		duk__cbor_decode_error(dec_ctx);
	}
}
#endif

DUK_LOCAL const duk_uint8_t *duk__cbor_decode_consume(duk_cbor_decode_context *dec_ctx, duk_size_t len) {
	DUK_ASSERT(dec_ctx->off <= dec_ctx->len);
	if (DUK_LIKELY(dec_ctx->len - dec_ctx->off >= len)) {
		const duk_uint8_t *res = dec_ctx->buf + dec_ctx->off;
		dec_ctx->off += len;
		return res;
	}

	duk__cbor_decode_error(dec_ctx);  /* Not enough input. */
	return NULL;
}

DUK_LOCAL int duk__cbor_decode_checkbreak(duk_cbor_decode_context *dec_ctx) {
	if (duk__cbor_decode_peekbyte(dec_ctx) == 0xffU) {
		DUK_ASSERT(dec_ctx->off < dec_ctx->len);
		dec_ctx->off++;
#if 0
		(void) duk__cbor_decode_readbyte(dec_ctx);
#endif
		return 1;
	}
	return 0;
}

DUK_LOCAL void duk__cbor_decode_push_aival_int(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib, duk_bool_t negative) {
	duk_uint8_t ai;
	duk_uint32_t t, t1, t2;
#if 0
	duk_uint64_t t3;
#endif
	duk_double_t d1, d2;
	duk_double_t d;

	ai = ib & 0x1fU;
	if (ai <= 0x17U) {
		t = ai;
		goto shared_exit;
	}

	switch (ai) {
	case 0x18U:  /* 1 byte */
		t = (duk_uint32_t) duk__cbor_decode_readbyte(dec_ctx);
		goto shared_exit;
	case 0x19U:  /* 2 byte */
		t = (duk_uint32_t) duk__cbor_decode_read_u16(dec_ctx);
		goto shared_exit;
	case 0x1aU:  /* 4 byte */
		t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx);
		goto shared_exit;
	case 0x1bU:  /* 8 byte */
		/* For uint64 it's important to handle the -1.0 part before
		 * casting to double: otherwise the adjustment might be lost
		 * in the cast.  Uses: -1.0 - d <=> -(d + 1.0).
		 */
		t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx);
		t2 = t;
		t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx);
		t1 = t;
#if 0
		t3 = (duk_uint64_t) t2 * DUK_U64_CONSTANT(0x100000000) + (duk_uint64_t) t1;
		if (negative) {
			if (t3 == DUK_UINT64_MAX) {
				/* -(0xffff'ffff'ffff'ffffULL + 1) =
				 * -0x1'0000'0000'0000'0000
				 *
				 * >>> -0x10000000000000000
				 * -18446744073709551616L
				 */
				return -18446744073709551616.0;
			} else {
				return -((duk_double_t) (t3 + DUK_U64_CONSTANT(1)));
			}
		} else {
			return (duk_double_t) t3;  /* XXX: cast helper */
		}
#endif
#if 0
		t3 = (duk_uint64_t) t2 * DUK_U64_CONSTANT(0x100000000) + (duk_uint64_t) t1;
		if (negative) {
			/* Simpler version: take advantage of the fact that
			 * 0xffff'ffff'ffff'ffff and 0x1'0000'0000'0000'0000
			 * both round to 0x1'0000'0000'0000'0000:
			 * > (0xffffffffffffffff).toString(16)
			 * '10000000000000000'
			 * > (0x10000000000000000).toString(16)
			 * '10000000000000000'
			 *
			 * For the DUK_UINT64_MAX case we just skip the +1
			 * increment to avoid wrapping; the result still
			 * comes out right for an IEEE double cast.
			 */
			if (t3 != DUK_UINT64_MAX) {
				t3++;
			}
			return -((duk_double_t) t3);
		} else {
			return (duk_double_t) t3;  /* XXX: cast helper */
		}
#endif
#if 1
		/* Use two double parts, avoids dependency on 64-bit type.
		 * Avoid precision loss carefully, especially when dealing
		 * with the required +1 for negative values.
		 *
		 * No fastint check for this path at present.
		 */
		d1 = (duk_double_t) t1;  /* XXX: cast helpers */
		d2 = (duk_double_t) t2 * 4294967296.0;
		if (negative) {
			d1 += 1.0;
		}
		d = d2 + d1;
		if (negative) {
			d = -d;
		}
#endif
		/* XXX: a push and check for fastint API would be nice */
		duk_push_number(dec_ctx->thr, d);
		return;
	}

	duk__cbor_decode_error(dec_ctx);
	return;

 shared_exit:
	if (negative) {
		/* XXX: a push and check for fastint API would be nice */
		if ((duk_uint_t) t <= (duk_uint_t) -(DUK_INT_MIN + 1)) {
			duk_push_int(dec_ctx->thr, -1 - ((duk_int_t) t));
		} else {
			duk_push_number(dec_ctx->thr, -1.0 - (duk_double_t) t);
		}
	} else {
		duk_push_uint(dec_ctx->thr, (duk_uint_t) t);
	}
}

DUK_LOCAL void duk__cbor_decode_skip_aival_int(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib) {
	const duk_int8_t skips[32] = {
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 8, -1, -1, -1, -1
	};
	duk_uint8_t ai;
	duk_int8_t skip;

	ai = ib & 0x1fU;
	skip = skips[ai];
	if (DUK_UNLIKELY(skip < 0)) {
		duk__cbor_decode_error(dec_ctx);
	}
	duk__cbor_decode_consume(dec_ctx, (duk_size_t) skip);
	return;
}

DUK_LOCAL duk_uint32_t duk__cbor_decode_aival_uint32(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib) {
	duk_uint8_t ai;
	duk_uint32_t t;

	ai = ib & 0x1fU;
	if (ai <= 0x17U) {
		return (duk_uint32_t) ai;
	}

	switch (ai) {
	case 0x18U:  /* 1 byte */
		t = (duk_uint32_t) duk__cbor_decode_readbyte(dec_ctx);
		return t;
	case 0x19U:  /* 2 byte */
		t = (duk_uint32_t) duk__cbor_decode_read_u16(dec_ctx);
		return t;
	case 0x1aU:  /* 4 byte */
		t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx);
		return t;
	case 0x1bU:  /* 8 byte */
		t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx);
		if (t != 0U) {
			break;
		}
		t = (duk_uint32_t) duk__cbor_decode_read_u32(dec_ctx);
		return t;
	}

	duk__cbor_decode_error(dec_ctx);
	return 0U;
}

DUK_LOCAL void duk__cbor_decode_buffer(duk_cbor_decode_context *dec_ctx, duk_uint8_t expected_base) {
	duk_uint32_t len;
	duk_uint8_t *buf;
	const duk_uint8_t *inp;
	duk_uint8_t ib;

	ib = duk__cbor_decode_readbyte(dec_ctx);
	if ((ib & 0xe0U) != expected_base) {
		duk__cbor_decode_error(dec_ctx);
	}
	/* Indefinite format is rejected by the following on purpose. */
	len = duk__cbor_decode_aival_uint32(dec_ctx, ib);
	inp = duk__cbor_decode_consume(dec_ctx, len);
	/* XXX: duk_push_fixed_buffer_with_data() would be a nice API addition. */
	buf = (duk_uint8_t *) duk_push_fixed_buffer(dec_ctx->thr, (duk_size_t) len);
	duk_memcpy((void *) buf, (const void *) inp, (size_t) len);
}

DUK_LOCAL void duk__cbor_decode_join_buffers(duk_cbor_decode_context *dec_ctx, duk_idx_t count) {
	duk_size_t total_size = 0;
	duk_idx_t top = duk_get_top(dec_ctx->thr);
	duk_idx_t base = top - count;  /* count is >= 1 */
	duk_idx_t idx;
	duk_uint8_t *p = NULL;

	DUK_ASSERT(count >= 1);
	DUK_ASSERT(top >= count);

	for (;;) {
		/* First round: compute total size.
		 * Second round: copy into place.
		 */
		for (idx = base; idx < top; idx++) {
			duk_uint8_t *buf_data;
			duk_size_t buf_size;

			buf_data = (duk_uint8_t *) duk_require_buffer(dec_ctx->thr, idx, &buf_size);
			if (p != NULL) {
				if (buf_size > 0U) {
					duk_memcpy((void *) p, (const void *) buf_data, buf_size);
				}
				p += buf_size;
			} else {
				total_size += buf_size;
				if (DUK_UNLIKELY(total_size < buf_size)) {  /* Wrap check. */
					duk__cbor_decode_error(dec_ctx);
				}
			}
		}

		if (p != NULL) {
			break;
		} else {
			p = (duk_uint8_t *) duk_push_fixed_buffer(dec_ctx->thr, total_size);
			DUK_ASSERT(p != NULL);
		}
	}

	duk_replace(dec_ctx->thr, base);
	duk_pop_n(dec_ctx->thr, count - 1);
}

DUK_LOCAL void duk__cbor_decode_and_join_strbuf(duk_cbor_decode_context *dec_ctx, duk_uint8_t expected_base) {
	duk_idx_t count = 0;
	for (;;) {
		if (duk__cbor_decode_checkbreak(dec_ctx)) {
			break;
		}
		duk_require_stack(dec_ctx->thr, 1);
		duk__cbor_decode_buffer(dec_ctx, expected_base);
		count++;
		if (DUK_UNLIKELY(count <= 0)) {  /* Wrap check. */
			duk__cbor_decode_error(dec_ctx);
		}
	}
	if (count == 0) {
		(void) duk_push_fixed_buffer(dec_ctx->thr, 0);
	} else if (count > 1) {
		duk__cbor_decode_join_buffers(dec_ctx, count);
	}
}

DUK_LOCAL duk_double_t duk__cbor_decode_half_float(duk_cbor_decode_context *dec_ctx) {
	duk_double_union u;
	const duk_uint8_t *inp;
	duk_int_t expt;
	duk_uint_t u16;
	duk_uint_t tmp;
	duk_double_t res;

	inp = duk__cbor_decode_consume(dec_ctx, 2);
	u16 = ((duk_uint_t) inp[0] << 8) + (duk_uint_t) inp[1];
	expt = (duk_int_t) ((u16 >> 10) & 0x1fU) - 15;

	/* Reconstruct IEEE double into little endian order first, then convert
	 * to host order.
	 */

	duk_memzero((void *) &u, sizeof(u));

	if (expt == -15) {
		/* Zero or denormal; but note that half float
		 * denormals become double normals.
		 */
		if ((u16 & 0x03ffU) == 0) {
			u.uc[7] = inp[0] & 0x80U;
		} else {
			/* Create denormal by first creating a double that
			 * contains the denormal bits and a leading implicit
			 * 1-bit.  Then subtract away the implicit 1-bit.
			 *
			 *    0.mmmmmmmmmm * 2^-14
			 *    1.mmmmmmmmmm 0.... * 2^-14
			 *   -1.0000000000 0.... * 2^-14
			 *
			 * Double exponent: -14 + 1023 = 0x3f1
			 */
			u.uc[7] = 0x3fU;
			u.uc[6] = 0x10U + (duk_uint8_t) ((u16 >> 6) & 0x0fU);
			u.uc[5] = (duk_uint8_t) ((u16 << 2) & 0xffU);  /* Mask is really 0xfcU */

			duk_dblunion_little_to_host(&u);
			res = u.d - 0.00006103515625;  /* 2^(-14) */
			if (u16 & 0x8000U) {
				res = -res;
			}
			return res;
		}
	} else if (expt == 16) {
		/* +/- Inf or NaN. */
		if ((u16 & 0x03ffU) == 0) {
			u.uc[7] = (inp[0] & 0x80U) + 0x7fU;
			u.uc[6] = 0xf0U;
		} else {
			/* Create a 'quiet NaN' with highest
			 * bit set (there are some platforms
			 * where the NaN payload convention is
			 * the opposite).  Keep sign.
			 */
			u.uc[7] = (inp[0] & 0x80U) + 0x7fU;
			u.uc[6] = 0xf8U;
		}
	} else {
		/* Normal. */
		tmp = (inp[0] & 0x80U) ? 0x80000000UL : 0UL;
		tmp += (duk_uint_t) (expt + 1023) << 20;
		tmp += (duk_uint_t) (inp[0] & 0x03U) << 18;
		tmp += (duk_uint_t) (inp[1] & 0xffU) << 10;
		u.uc[7] = (tmp >> 24) & 0xffU;
		u.uc[6] = (tmp >> 16) & 0xffU;
		u.uc[5] = (tmp >> 8) & 0xffU;
		u.uc[4] = (tmp >> 0) & 0xffU;
	}

	duk_dblunion_little_to_host(&u);
	return u.d;
}

DUK_LOCAL void duk__cbor_decode_string(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib, duk_uint8_t ai) {
	/* If the CBOR string data is not valid UTF-8 it is technically
	 * invalid CBOR.  Possible behaviors at least:
	 *
	 *   1. Reject the input, i.e. throw TypeError.
	 *
	 *   2. Accept the input, but sanitize non-UTF-8 data into UTF-8
	 *      using U+FFFD replacements.  Also it might make sense to
	 *      decode non-BMP codepoints into surrogates for better
	 *      ECMAScript compatibility.
	 *
	 *   3. Accept the input as a Duktape string (which are not always
	 *      valid UTF-8), but reject any input that would create a
	 *      Symbol representation.
	 *
	 * Current behavior is 3.
	 */

	if (ai == 0x1fU) {
		duk_uint8_t *buf_data;
		duk_size_t buf_size;

		duk__cbor_decode_and_join_strbuf(dec_ctx, 0x60U);
		buf_data = (duk_uint8_t *) duk_require_buffer(dec_ctx->thr, -1, &buf_size);
		(void) duk_push_lstring(dec_ctx->thr, (const char *) buf_data, buf_size);
		duk_remove(dec_ctx->thr, -2);
	} else {
		duk_uint32_t len;
		const duk_uint8_t *inp;

		len = duk__cbor_decode_aival_uint32(dec_ctx, ib);
		inp = duk__cbor_decode_consume(dec_ctx, len);
		(void) duk_push_lstring(dec_ctx->thr, (const char *) inp, (duk_size_t) len);
	}
	if (duk_is_symbol(dec_ctx->thr, -1)) {
		/* Refuse to create Symbols when decoding. */
		duk__cbor_decode_error(dec_ctx);
	}

	/* XXX: Here a Duktape API call to convert input -> utf-8 with
	 * replacements would be nice.
	 */
}

DUK_LOCAL duk_bool_t duk__cbor_decode_array(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib, duk_uint8_t ai) {
	duk_uint32_t idx, len;

	duk__cbor_req_stack(dec_ctx);

	/* Support arrays up to 0xfffffffeU in length.  0xffffffff is
	 * used as an indefinite length marker.
	 */
	if (ai == 0x1fU) {
		len = 0xffffffffUL;
	} else {
		len = duk__cbor_decode_aival_uint32(dec_ctx, ib);
		if (len == 0xffffffffUL) {
			return 0;
		}
	}

	/* XXX: use bare array? */
	duk_push_array(dec_ctx->thr);
	for (idx = 0U; ;) {
		if (len == 0xffffffffUL && duk__cbor_decode_checkbreak(dec_ctx)) {
			break;
		}
		if (idx == len) {
			if (ai == 0x1fU) {
				return 0;
			}
			break;
		}
		duk__cbor_decode_value(dec_ctx);
		duk_put_prop_index(dec_ctx->thr, -2, (duk_uarridx_t) idx);
		idx++;
		if (idx == 0U) {
			return 0;  /* wrapped */
		}
	}

	return 1;
}

DUK_LOCAL duk_bool_t duk__cbor_decode_map(duk_cbor_decode_context *dec_ctx, duk_uint8_t ib, duk_uint8_t ai) {
	duk_uint32_t count;

	duk__cbor_req_stack(dec_ctx);

	if (ai == 0x1fU) {
		count = 0xffffffffUL;
	} else {
		count = duk__cbor_decode_aival_uint32(dec_ctx, ib);
		if (count == 0xffffffffUL) {
			return 0;
		}
	}

	/* XXX: use bare object? */
	duk_push_object(dec_ctx->thr);
	for (;;) {
		if (count == 0xffffffffUL) {
			if (duk__cbor_decode_checkbreak(dec_ctx)) {
				break;
			}
		} else {
			if (count == 0UL) {
				break;
			}
			count--;
		}

		/* Non-string keys are coerced to strings,
		 * possibly leading to overwriting previous
		 * keys.  Last key of a certain coerced name
		 * wins.  If key is an object, it will coerce
		 * to '[object Object]' which is consistent
		 * but potentially misleading.  One alternative
		 * would be to skip non-string keys.
		 */
		duk__cbor_decode_value(dec_ctx);
		duk__cbor_decode_value(dec_ctx);
		duk_put_prop(dec_ctx->thr, -3);
	}

	return 1;
}

DUK_LOCAL duk_double_t duk__cbor_decode_float(duk_cbor_decode_context *dec_ctx) {
	duk_float_union u;
	const duk_uint8_t *inp;
	inp = duk__cbor_decode_consume(dec_ctx, 4);
	duk_memcpy((void *) u.uc, (const void *) inp, 4);
	duk_fltunion_big_to_host(&u);
	return (duk_double_t) u.f;
}

DUK_LOCAL duk_double_t duk__cbor_decode_double(duk_cbor_decode_context *dec_ctx) {
	duk_double_union u;
	const duk_uint8_t *inp;
	inp = duk__cbor_decode_consume(dec_ctx, 8);
	duk_memcpy((void *) u.uc, (const void *) inp, 8);
	duk_dblunion_big_to_host(&u);
	return u.d;
}

#if defined(DUK_CBOR_DECODE_FASTPATH)
#define DUK__CBOR_AI  (ib & 0x1fU)

DUK_LOCAL void duk__cbor_decode_value(duk_cbor_decode_context *dec_ctx) {
	duk_uint8_t ib;

	/* Any paths potentially recursing back to duk__cbor_decode_value()
	 * must perform a Duktape value stack growth check.  Avoid the check
	 * here for simple paths like primitive values.
	 */

 reread_initial_byte:
	DUK_DDD(DUK_DDDPRINT("cbor decode off=%ld len=%ld", (long) dec_ctx->off, (long) dec_ctx->len));

	ib = duk__cbor_decode_readbyte(dec_ctx);

	/* Full initial byte switch, footprint cost over baseline is ~+1kB. */
	/* XXX: Force full switch with no range check. */

	switch (ib) {
	case 0x00U: case 0x01U: case 0x02U: case 0x03U: case 0x04U: case 0x05U: case 0x06U: case 0x07U:
	case 0x08U: case 0x09U: case 0x0aU: case 0x0bU: case 0x0cU: case 0x0dU: case 0x0eU: case 0x0fU:
	case 0x10U: case 0x11U: case 0x12U: case 0x13U: case 0x14U: case 0x15U: case 0x16U: case 0x17U:
		duk_push_uint(dec_ctx->thr, ib);
		break;
	case 0x18U: case 0x19U: case 0x1aU: case 0x1bU:
		duk__cbor_decode_push_aival_int(dec_ctx, ib, 0 /*negative*/);
		break;
	case 0x1cU: case 0x1dU: case 0x1eU: case 0x1fU:
		goto format_error;
	case 0x20U: case 0x21U: case 0x22U: case 0x23U: case 0x24U: case 0x25U: case 0x26U: case 0x27U:
	case 0x28U: case 0x29U: case 0x2aU: case 0x2bU: case 0x2cU: case 0x2dU: case 0x2eU: case 0x2fU:
	case 0x30U: case 0x31U: case 0x32U: case 0x33U: case 0x34U: case 0x35U: case 0x36U: case 0x37U:
		duk_push_int(dec_ctx->thr, -((duk_int_t) ((ib - 0x20U) + 1U)));
		break;
	case 0x38U: case 0x39U: case 0x3aU: case 0x3bU:
		duk__cbor_decode_push_aival_int(dec_ctx, ib, 1 /*negative*/);
		break;
	case 0x3cU: case 0x3dU: case 0x3eU: case 0x3fU:
		goto format_error;
	case 0x40U: case 0x41U: case 0x42U: case 0x43U: case 0x44U: case 0x45U: case 0x46U: case 0x47U:
	case 0x48U: case 0x49U: case 0x4aU: case 0x4bU: case 0x4cU: case 0x4dU: case 0x4eU: case 0x4fU:
	case 0x50U: case 0x51U: case 0x52U: case 0x53U: case 0x54U: case 0x55U: case 0x56U: case 0x57U:
		/* XXX: Avoid rewind, we know the length already. */
		DUK_ASSERT(dec_ctx->off > 0U);
		dec_ctx->off--;
		duk__cbor_decode_buffer(dec_ctx, 0x40U);
		break;
	case 0x58U: case 0x59U: case 0x5aU: case 0x5bU:
		/* XXX: Avoid rewind, decode length inline. */
		DUK_ASSERT(dec_ctx->off > 0U);
		dec_ctx->off--;
		duk__cbor_decode_buffer(dec_ctx, 0x40U);
		break;
	case 0x5cU: case 0x5dU: case 0x5eU:
		goto format_error;
	case 0x5fU:
		duk__cbor_decode_and_join_strbuf(dec_ctx, 0x40U);
		break;
	case 0x60U: case 0x61U: case 0x62U: case 0x63U: case 0x64U: case 0x65U: case 0x66U: case 0x67U:
	case 0x68U: case 0x69U: case 0x6aU: case 0x6bU: case 0x6cU: case 0x6dU: case 0x6eU: case 0x6fU:
	case 0x70U: case 0x71U: case 0x72U: case 0x73U: case 0x74U: case 0x75U: case 0x76U: case 0x77U:
		/* XXX: Avoid double decode of length. */
		duk__cbor_decode_string(dec_ctx, ib, DUK__CBOR_AI);
		break;
	case 0x78U: case 0x79U: case 0x7aU: case 0x7bU:
		/* XXX: Avoid double decode of length. */
		duk__cbor_decode_string(dec_ctx, ib, DUK__CBOR_AI);
		break;
	case 0x7cU: case 0x7dU: case 0x7eU:
		goto format_error;
	case 0x7fU:
		duk__cbor_decode_string(dec_ctx, ib, DUK__CBOR_AI);
		break;
	case 0x80U: case 0x81U: case 0x82U: case 0x83U: case 0x84U: case 0x85U: case 0x86U: case 0x87U:
	case 0x88U: case 0x89U: case 0x8aU: case 0x8bU: case 0x8cU: case 0x8dU: case 0x8eU: case 0x8fU:
	case 0x90U: case 0x91U: case 0x92U: case 0x93U: case 0x94U: case 0x95U: case 0x96U: case 0x97U:
		if (DUK_UNLIKELY(duk__cbor_decode_array(dec_ctx, ib, DUK__CBOR_AI) == 0)) {
			goto format_error;
		}
		break;
	case 0x98U: case 0x99U: case 0x9aU: case 0x9bU:
		if (DUK_UNLIKELY(duk__cbor_decode_array(dec_ctx, ib, DUK__CBOR_AI) == 0)) {
			goto format_error;
		}
		break;
	case 0x9cU: case 0x9dU: case 0x9eU:
		goto format_error;
	case 0x9fU:
		if (DUK_UNLIKELY(duk__cbor_decode_array(dec_ctx, ib, DUK__CBOR_AI) == 0)) {
			goto format_error;
		}
		break;
	case 0xa0U: case 0xa1U: case 0xa2U: case 0xa3U: case 0xa4U: case 0xa5U: case 0xa6U: case 0xa7U:
	case 0xa8U: case 0xa9U: case 0xaaU: case 0xabU: case 0xacU: case 0xadU: case 0xaeU: case 0xafU:
	case 0xb0U: case 0xb1U: case 0xb2U: case 0xb3U: case 0xb4U: case 0xb5U: case 0xb6U: case 0xb7U:
		if (DUK_UNLIKELY(duk__cbor_decode_map(dec_ctx, ib, DUK__CBOR_AI) == 0)) {
			goto format_error;
		}
		break;
	case 0xb8U: case 0xb9U: case 0xbaU: case 0xbbU:
		if (DUK_UNLIKELY(duk__cbor_decode_map(dec_ctx, ib, DUK__CBOR_AI) == 0)) {
			goto format_error;
		}
		break;
	case 0xbcU: case 0xbdU: case 0xbeU:
		goto format_error;
	case 0xbfU:
		if (DUK_UNLIKELY(duk__cbor_decode_map(dec_ctx, ib, DUK__CBOR_AI) == 0)) {
			goto format_error;
		}
		break;
	case 0xc0U: case 0xc1U: case 0xc2U: case 0xc3U: case 0xc4U: case 0xc5U: case 0xc6U: case 0xc7U:
	case 0xc8U: case 0xc9U: case 0xcaU: case 0xcbU: case 0xccU: case 0xcdU: case 0xceU: case 0xcfU:
	case 0xd0U: case 0xd1U: case 0xd2U: case 0xd3U: case 0xd4U: case 0xd5U: case 0xd6U: case 0xd7U:
		/* Tag 0-23: drop. */
		goto reread_initial_byte;
	case 0xd8U: case 0xd9U: case 0xdaU: case 0xdbU:
		duk__cbor_decode_skip_aival_int(dec_ctx, ib);
		goto reread_initial_byte;
	case 0xdcU: case 0xddU: case 0xdeU: case 0xdfU:
		goto format_error;
	case 0xe0U:
		goto format_error;
	case 0xe1U:
		goto format_error;
	case 0xe2U:
		goto format_error;
	case 0xe3U:
		goto format_error;
	case 0xe4U:
		goto format_error;
	case 0xe5U:
		goto format_error;
	case 0xe6U:
		goto format_error;
	case 0xe7U:
		goto format_error;
	case 0xe8U:
		goto format_error;
	case 0xe9U:
		goto format_error;
	case 0xeaU:
		goto format_error;
	case 0xebU:
		goto format_error;
	case 0xecU:
		goto format_error;
	case 0xedU:
		goto format_error;
	case 0xeeU:
		goto format_error;
	case 0xefU:
		goto format_error;
	case 0xf0U:
		goto format_error;
	case 0xf1U:
		goto format_error;
	case 0xf2U:
		goto format_error;
	case 0xf3U:
		goto format_error;
	case 0xf4U:
		duk_push_false(dec_ctx->thr);
		break;
	case 0xf5U:
		duk_push_true(dec_ctx->thr);
		break;
	case 0xf6U:
		duk_push_null(dec_ctx->thr);
		break;
	case 0xf7U:
		duk_push_undefined(dec_ctx->thr);
		break;
	case 0xf8U:
		/* Simple value 32-255, nothing defined yet, so reject. */
		goto format_error;
	case 0xf9U: {
		duk_double_t d;
		d = duk__cbor_decode_half_float(dec_ctx);
		duk_push_number(dec_ctx->thr, d);
		break;
	}
	case 0xfaU: {
		duk_double_t d;
		d = duk__cbor_decode_float(dec_ctx);
		duk_push_number(dec_ctx->thr, d);
		break;
	}
	case 0xfbU: {
		duk_double_t d;
		d = duk__cbor_decode_double(dec_ctx);
		duk_push_number(dec_ctx->thr, d);
		break;
	}
	case 0xfcU:
	case 0xfdU:
	case 0xfeU:
	case 0xffU:
		goto format_error;
	}  /* end switch */

	return;

 format_error:
	duk__cbor_decode_error(dec_ctx);
}
#else  /* DUK_CBOR_DECODE_FASTPATH */
DUK_LOCAL void duk__cbor_decode_value(duk_cbor_decode_context *dec_ctx) {
	duk_uint8_t ib, mt, ai;

	/* Any paths potentially recursing back to duk__cbor_decode_value()
	 * must perform a Duktape value stack growth check.  Avoid the check
	 * here for simple paths like primitive values.
	 */

 reread_initial_byte:
	DUK_DDD(DUK_DDDPRINT("cbor decode off=%ld len=%ld", (long) dec_ctx->off, (long) dec_ctx->len));

	ib = duk__cbor_decode_readbyte(dec_ctx);
	mt = ib >> 5U;
	ai = ib & 0x1fU;

	/* Additional information in [24,27] = [0x18,0x1b] has relatively
	 * uniform handling for all major types: read 1/2/4/8 additional
	 * bytes.  For major type 7 the 1-byte value is a 'simple type', and
	 * 2/4/8-byte values are floats.  For other major types the 1/2/4/8
	 * byte values are integers.  The lengths are uniform, but the typing
	 * is not.
	 */

	switch (mt) {
	case 0U: {  /* unsigned integer */
		duk__cbor_decode_push_aival_int(dec_ctx, ib, 0 /*negative*/);
		break;
	}
	case 1U: {  /* negative integer */
		duk__cbor_decode_push_aival_int(dec_ctx, ib, 1 /*negative*/);
		break;
	}
	case 2U: {  /* byte string */
		if (ai == 0x1fU) {
			duk__cbor_decode_and_join_strbuf(dec_ctx, 0x40U);
		} else {
			duk__cbor_decode_rewind(dec_ctx, 1U);
			duk__cbor_decode_buffer(dec_ctx, 0x40U);
		}
		break;
	}
	case 3U: {  /* text string */
		duk__cbor_decode_string(dec_ctx, ib, ai);
		break;
	}
	case 4U: {  /* array of data items */
		if (DUK_UNLIKELY(duk__cbor_decode_array(dec_ctx, ib, ai) == 0)) {
			goto format_error;
		}
		break;
	}
	case 5U: {  /* map of pairs of data items */
		if (DUK_UNLIKELY(duk__cbor_decode_map(dec_ctx, ib, ai) == 0)) {
			goto format_error;
		}
		break;
	}
	case 6U: {  /* semantic tagging */
		/* Tags are ignored now, re-read initial byte.  A tagged
		 * value may itself be tagged (an unlimited number of times)
		 * so keep on peeling away tags.
		 */
		duk__cbor_decode_skip_aival_int(dec_ctx, ib);
		goto reread_initial_byte;
	}
	case 7U: {  /* floating point numbers, simple data types, break; other */
		switch (ai) {
		case 0x14U: {
			duk_push_false(dec_ctx->thr);
			break;
		}
		case 0x15U: {
			duk_push_true(dec_ctx->thr);
			break;
		}
		case 0x16U: {
			duk_push_null(dec_ctx->thr);
			break;
		}
		case 0x17U: {
			duk_push_undefined(dec_ctx->thr);
			break;
		}
		case 0x18U: {  /* more simple values (1 byte) */
			/* Simple value encoded in additional byte (none
			 * are defined so far).  RFC 7049 states that the
			 * follow-up byte must be 32-255 to minimize
			 * confusion.  So, a non-shortest encoding like
			 * f815 (= true, shortest encoding f5) must be
			 * rejected.  cbor.me tester rejects f815, but
			 * e.g. Python CBOR binding decodes it as true.
			 */
			goto format_error;
		}
		case 0x19U: {  /* half-float (2 bytes) */
			duk_double_t d;
			d = duk__cbor_decode_half_float(dec_ctx);
			duk_push_number(dec_ctx->thr, d);
			break;
		}
		case 0x1aU: {  /* float (4 bytes) */
			duk_double_t d;
			d = duk__cbor_decode_float(dec_ctx);
			duk_push_number(dec_ctx->thr, d);
			break;
		}
		case 0x1bU: {  /* double (8 bytes) */
			duk_double_t d;
			d = duk__cbor_decode_double(dec_ctx);
			duk_push_number(dec_ctx->thr, d);
			break;
		}
		case 0xffU:  /* unexpected break */
		default: {
			goto format_error;
		}
		}  /* end switch */
		break;
	}
	default: {
		goto format_error;  /* will never actually occur */
	}
	}  /* end switch */

	return;

 format_error:
	duk__cbor_decode_error(dec_ctx);
}
#endif  /* DUK_CBOR_DECODE_FASTPATH */

DUK_LOCAL void duk__cbor_encode(duk_hthread *thr, duk_idx_t idx, duk_uint_t encode_flags) {
	duk_cbor_encode_context enc_ctx;
	duk_uint8_t *buf;

	DUK_UNREF(encode_flags);

	idx = duk_require_normalize_index(thr, idx);

	enc_ctx.thr = thr;
	enc_ctx.idx_buf = duk_get_top(thr);

	enc_ctx.len = 64;
	buf = (duk_uint8_t *) duk_push_dynamic_buffer(thr, enc_ctx.len);
	enc_ctx.ptr = buf;
	enc_ctx.buf = buf;
	enc_ctx.buf_end = buf + enc_ctx.len;

	duk_dup(thr, idx);
	duk__cbor_encode_value(&enc_ctx);
	duk_resize_buffer(enc_ctx.thr, enc_ctx.idx_buf, (duk_size_t) (enc_ctx.ptr - enc_ctx.buf));
	duk_replace(thr, idx);
}

DUK_LOCAL void duk__cbor_decode(duk_hthread *thr, duk_idx_t idx, duk_uint_t decode_flags) {
	duk_cbor_decode_context dec_ctx;

	DUK_UNREF(decode_flags);

	/* Suppress compile warnings for functions only needed with e.g.
	 * asserts enabled.
	 */
	DUK_UNREF(duk__cbor_get_reserve);

	idx = duk_require_normalize_index(thr, idx);

	dec_ctx.thr = thr;
	dec_ctx.buf = (const duk_uint8_t *) duk_require_buffer_data(thr, idx, &dec_ctx.len);
	dec_ctx.off = 0;
	/* dec_ctx.len: set above */

	duk__cbor_req_stack(&dec_ctx);
	duk__cbor_decode_value(&dec_ctx);
	if (dec_ctx.off != dec_ctx.len) {
		(void) duk_type_error(thr, "trailing garbage");
	}

	duk_replace(thr, idx);
}

#else  /* DUK_USE_CBOR_SUPPORT */

DUK_LOCAL void duk__cbor_encode(duk_hthread *thr, duk_idx_t idx, duk_uint_t encode_flags) {
	DUK_UNREF(idx);
	DUK_UNREF(encode_flags);
	DUK_ERROR_UNSUPPORTED(thr);
}

DUK_LOCAL void duk__cbor_decode(duk_hthread *thr, duk_idx_t idx, duk_uint_t decode_flags) {
	DUK_UNREF(idx);
	DUK_UNREF(decode_flags);
	DUK_ERROR_UNSUPPORTED(thr);
}

#endif  /* DUK_USE_CBOR_SUPPORT */

/*
 *  Public APIs
 */

DUK_EXTERNAL void duk_cbor_encode(duk_hthread *thr, duk_idx_t idx, duk_uint_t encode_flags) {
	DUK_ASSERT_API_ENTRY(thr);
	duk__cbor_encode(thr, idx, encode_flags);
}
DUK_EXTERNAL void duk_cbor_decode(duk_hthread *thr, duk_idx_t idx, duk_uint_t decode_flags) {
	DUK_ASSERT_API_ENTRY(thr);
	duk__cbor_decode(thr, idx, decode_flags);
}

#if defined(DUK_USE_CBOR_BUILTIN)
#if defined(DUK_USE_CBOR_SUPPORT)
DUK_INTERNAL duk_ret_t duk_bi_cbor_encode(duk_hthread *thr) {
	DUK_ASSERT_TOP(thr, 1);

	duk__cbor_encode(thr, -1, 0 /*flags*/);

	/* Produce an ArrayBuffer by first decoding into a plain buffer which
	 * mimics a Uint8Array and gettings its .buffer property.
	 */
	/* XXX: shortcut */
	(void) duk_get_prop_stridx(thr, -1, DUK_STRIDX_LC_BUFFER);
	return 1;
}

DUK_INTERNAL duk_ret_t duk_bi_cbor_decode(duk_hthread *thr) {
	DUK_ASSERT_TOP(thr, 1);

	duk__cbor_decode(thr, -1, 0 /*flags*/);
	return 1;
}
#else  /* DUK_USE_CBOR_SUPPORT */
DUK_INTERNAL duk_ret_t duk_bi_cbor_encode(duk_hthread *thr) {
	DUK_ERROR_UNSUPPORTED(thr);
	DUK_WO_NORETURN(return 0;);
}
DUK_INTERNAL duk_ret_t duk_bi_cbor_decode(duk_hthread *thr) {
	DUK_ERROR_UNSUPPORTED(thr);
	DUK_WO_NORETURN(return 0;);
}
#endif  /* DUK_USE_CBOR_SUPPORT */
#endif  /* DUK_USE_CBOR_BUILTIN */
