/*
 *  Encoding and decoding basic formats: hex, base64.
 *
 *  These are in-place operations which may allow an optimized implementation.
 *
 *  Base-64: https://tools.ietf.org/html/rfc4648#section-4
 */

#include "duk_internal.h"

/*
 *  Misc helpers
 */

/* Shared handling for encode/decode argument.  Fast path handling for
 * buffer and string values because they're the most common.  In particular,
 * avoid creating a temporary string or buffer when possible.  Return value
 * is guaranteed to be non-NULL, even for zero length input.
 */
DUK_LOCAL const duk_uint8_t *duk__prep_codec_arg(duk_hthread *thr, duk_idx_t idx, duk_size_t *out_len) {
	const void *def_ptr = (const void *) out_len;  /* Any non-NULL pointer will do. */
	const void *ptr;
	duk_bool_t isbuffer;

	DUK_ASSERT(out_len != NULL);
	DUK_ASSERT(def_ptr != NULL);
	DUK_ASSERT(duk_is_valid_index(thr, idx));  /* checked by caller */

	ptr = (const void *) duk_get_buffer_data_raw(thr, idx, out_len, NULL /*def_ptr*/, 0 /*def_size*/, 0 /*throw_flag*/, &isbuffer);
	if (isbuffer) {
		DUK_ASSERT(ptr != NULL || *out_len == 0U);
		if (DUK_UNLIKELY(ptr == NULL)) {
			ptr = def_ptr;
		}
		DUK_ASSERT(ptr != NULL);
	} else {
		/* For strings a non-NULL pointer is always guaranteed because
		 * at least a NUL will be present.
		 */
		ptr = (const void *) duk_to_lstring(thr, idx, out_len);
		DUK_ASSERT(ptr != NULL);
	}
	DUK_ASSERT(ptr != NULL);
	return (const duk_uint8_t *) ptr;
}

/*
 *  Base64
 */

#if defined(DUK_USE_BASE64_SUPPORT)
/* Bytes emitted for number of padding characters in range [0,4]. */
DUK_LOCAL const duk_int8_t duk__base64_decode_nequal_step[5] = {
	3,   /* #### -> 24 bits, emit 3 bytes */
	2,   /* ###= -> 18 bits, emit 2 bytes */
	1,   /* ##== -> 12 bits, emit 1 byte */
	-1,  /* #=== -> 6 bits, error */
	0,   /* ==== -> 0 bits, emit 0 bytes */
};

#if defined(DUK_USE_BASE64_FASTPATH)
DUK_LOCAL const duk_uint8_t duk__base64_enctab_fast[64] = {
	0x41U, 0x42U, 0x43U, 0x44U, 0x45U, 0x46U, 0x47U, 0x48U, 0x49U, 0x4aU, 0x4bU, 0x4cU, 0x4dU, 0x4eU, 0x4fU, 0x50U,  /* A...P */
	0x51U, 0x52U, 0x53U, 0x54U, 0x55U, 0x56U, 0x57U, 0x58U, 0x59U, 0x5aU, 0x61U, 0x62U, 0x63U, 0x64U, 0x65U, 0x66U,  /* Q...f */
	0x67U, 0x68U, 0x69U, 0x6aU, 0x6bU, 0x6cU, 0x6dU, 0x6eU, 0x6fU, 0x70U, 0x71U, 0x72U, 0x73U, 0x74U, 0x75U, 0x76U,  /* g...v */
	0x77U, 0x78U, 0x79U, 0x7aU, 0x30U, 0x31U, 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, 0x38U, 0x39U, 0x2bU, 0x2fU   /* w.../ */
};
#endif  /* DUK_USE_BASE64_FASTPATH */

#if defined(DUK_USE_BASE64_FASTPATH)
/* Decode table for one byte of input:
 *   -1 = allowed whitespace
 *   -2 = padding
 *   -3 = error
 *    0...63 decoded bytes
 */
DUK_LOCAL const duk_int8_t duk__base64_dectab_fast[256] = {
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -1, -1, -3, -3, -1, -3, -3,  /* 0x00...0x0f */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,  /* 0x10...0x1f */
	-1, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, 62, -3, -3, -3, 63,  /* 0x20...0x2f */
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -3, -3, -3, -2, -3, -3,  /* 0x30...0x3f */
	-3,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 0x40...0x4f */
	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -3, -3, -3, -3, -3,  /* 0x50...0x5f */
	-3, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,  /* 0x60...0x6f */
	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -3, -3, -3, -3, -3,  /* 0x70...0x7f */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,  /* 0x80...0x8f */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,  /* 0x90...0x9f */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,  /* 0xa0...0xaf */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,  /* 0xb0...0xbf */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,  /* 0xc0...0xcf */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,  /* 0xd0...0xdf */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,  /* 0xe0...0xef */
	-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3   /* 0xf0...0xff */
};
#endif  /* DUK_USE_BASE64_FASTPATH */

#if defined(DUK_USE_BASE64_FASTPATH)
DUK_LOCAL DUK_ALWAYS_INLINE void duk__base64_encode_fast_3(const duk_uint8_t *src, duk_uint8_t *dst) {
	duk_uint_t t;

	t = (duk_uint_t) src[0];
	t = (t << 8) + (duk_uint_t) src[1];
	t = (t << 8) + (duk_uint_t) src[2];

	dst[0] = duk__base64_enctab_fast[t >> 18];
	dst[1] = duk__base64_enctab_fast[(t >> 12) & 0x3fU];
	dst[2] = duk__base64_enctab_fast[(t >> 6) & 0x3fU];
	dst[3] = duk__base64_enctab_fast[t & 0x3fU];

#if 0
	/* Tested: not faster on x64, most likely due to aliasing between
	 * output and input index computation.
	 */
	/* aaaaaabb bbbbcccc ccdddddd */
	dst[0] = duk__base64_enctab_fast[(src[0] >> 2) & 0x3fU];
	dst[1] = duk__base64_enctab_fast[((src[0] << 4) & 0x30U) | ((src[1] >> 4) & 0x0fU)];
	dst[2] = duk__base64_enctab_fast[((src[1] << 2) & 0x3fU) | ((src[2] >> 6) & 0x03U)];
	dst[3] = duk__base64_enctab_fast[src[2] & 0x3fU];
#endif
}

DUK_LOCAL DUK_ALWAYS_INLINE void duk__base64_encode_fast_2(const duk_uint8_t *src, duk_uint8_t *dst) {
	duk_uint_t t;

	t = (duk_uint_t) src[0];
	t = (t << 8) + (duk_uint_t) src[1];
	dst[0] = duk__base64_enctab_fast[t >> 10];           /* XXXXXX-- -------- */
	dst[1] = duk__base64_enctab_fast[(t >> 4) & 0x3fU];  /* ------XX XXXX---- */
	dst[2] = duk__base64_enctab_fast[(t << 2) & 0x3fU];  /* -------- ----XXXX */
	dst[3] = DUK_ASC_EQUALS;
}

DUK_LOCAL DUK_ALWAYS_INLINE void duk__base64_encode_fast_1(const duk_uint8_t *src, duk_uint8_t *dst) {
	duk_uint_t t;

	t = (duk_uint_t) src[0];
	dst[0] = duk__base64_enctab_fast[t >> 2];            /* XXXXXX-- */
	dst[1] = duk__base64_enctab_fast[(t << 4) & 0x3fU];  /* ------XX */
	dst[2] = DUK_ASC_EQUALS;
	dst[3] = DUK_ASC_EQUALS;
}

DUK_LOCAL void duk__base64_encode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst) {
	duk_size_t n;
	const duk_uint8_t *p;
	duk_uint8_t *q;

	n = srclen;
	p = src;
	q = dst;

	if (n >= 16U) {
		/* Fast path, unrolled by 4, allows interleaving.  Process
		 * 12-byte input chunks which encode to 16-char output chunks.
		 * Only enter when at least one block is emitted (avoids div+mul
		 * for short inputs too).
		 */
		const duk_uint8_t *p_end_fast;

		p_end_fast = p + ((n / 12U) * 12U);
		DUK_ASSERT(p_end_fast >= p + 12);
		do {
			duk__base64_encode_fast_3(p, q);
			duk__base64_encode_fast_3(p + 3, q + 4);
			duk__base64_encode_fast_3(p + 6, q + 8);
			duk__base64_encode_fast_3(p + 9, q + 12);
			p += 12;
			q += 16;
		} while (DUK_LIKELY(p != p_end_fast));

		DUK_ASSERT(src + srclen >= p);
		n = (duk_size_t) (src + srclen - p);
		DUK_ASSERT(n < 12U);
	}

	/* Remainder. */
	while (n >= 3U) {
		duk__base64_encode_fast_3(p, q);
		p += 3;
		q += 4;
		n -= 3U;
	}
	DUK_ASSERT(n == 0U || n == 1U || n == 2U);
	if (n == 1U) {
		duk__base64_encode_fast_1(p, q);
#if 0  /* Unnecessary. */
		p += 1;
		q += 4;
		n -= 1U;
#endif
	} else if (n == 2U) {
		duk__base64_encode_fast_2(p, q);
#if 0  /* Unnecessary. */
		p += 2;
		q += 4;
		n -= 2U;
#endif
	} else {
		DUK_ASSERT(n == 0U);  /* nothing to do */
		;
	}
}
#else  /* DUK_USE_BASE64_FASTPATH */
DUK_LOCAL void duk__base64_encode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst) {
	duk_small_uint_t i, npad;
	duk_uint_t t, x, y;
	const duk_uint8_t *p;
	const duk_uint8_t *p_end;
	duk_uint8_t *q;

	p = src;
	p_end = src + srclen;
	q = dst;
	npad = 0U;

	while (p < p_end) {
		/* Read 3 bytes into 't', padded by zero. */
		t = 0;
		for (i = 0; i < 3; i++) {
			t = t << 8;
			if (p < p_end) {
				t += (duk_uint_t) (*p++);
			} else {
				/* This only happens on the last loop and we're
				 * guaranteed to exit on the next loop.
				 */
				npad++;
			}
		}
		DUK_ASSERT(npad <= 2U);

		/* Emit 4 encoded characters.  If npad > 0, some of the
		 * chars will be incorrect (zero bits) but we fix up the
		 * padding after the loop.  A straightforward 64-byte
		 * lookup would be faster and cleaner, but this is shorter.
		 */
		for (i = 0; i < 4; i++) {
			x = ((t >> 18) & 0x3fU);
			t = t << 6;

			if (x <= 51U) {
				if (x <= 25) {
					y = x + DUK_ASC_UC_A;
				} else {
					y = x - 26 + DUK_ASC_LC_A;
				}
			} else {
				if (x <= 61U) {
					y = x - 52 + DUK_ASC_0;
				} else if (x == 62) {
					y = DUK_ASC_PLUS;
				} else {
					DUK_ASSERT(x == 63);
					y = DUK_ASC_SLASH;
				}
			}

			*q++ = (duk_uint8_t) y;
		}
	}

	/* Handle padding by rewriting 0-2 bogus characters at the end.
	 *
	 *  Missing bytes    npad     base64 example
	 *    0               0         ####
	 *    1               1         ###=
	 *    2               2         ##==
	 */
	DUK_ASSERT(npad <= 2U);
	while (npad > 0U) {
		*(q - npad) = DUK_ASC_EQUALS;
		npad--;
	}
}
#endif  /* DUK_USE_BASE64_FASTPATH */

#if defined(DUK_USE_BASE64_FASTPATH)
DUK_LOCAL duk_bool_t duk__base64_decode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst, duk_uint8_t **out_dst_final) {
	duk_int_t x;
	duk_uint_t t;
	duk_small_uint_t n_equal;
	duk_int8_t step;
	const duk_uint8_t *p;
	const duk_uint8_t *p_end;
	const duk_uint8_t *p_end_safe;
	duk_uint8_t *q;

	DUK_ASSERT(src != NULL);  /* Required by pointer arithmetic below, which fails for NULL. */

	p = src;
	p_end = src + srclen;
	p_end_safe = p_end - 8;  /* If 'src <= src_end_safe', safe to read 8 bytes. */
	q = dst;

	/* Alternate between a fast path which processes clean groups with no
	 * padding or whitespace, and a slow path which processes one arbitrary
	 * group and then re-enters the fast path.  This handles e.g. base64
	 * with newlines reasonably well because the majority of a line is in
	 * the fast path.
	 */
	for (;;) {
		/* Fast path, on each loop handle two 4-char input groups.
		 * If both are clean, emit 6 bytes and continue.  If first
		 * is clean, emit 3 bytes and drop out; otherwise emit
		 * nothing and drop out.  This approach could be extended to
		 * more groups per loop, but for inputs with e.g. periodic
		 * newlines (which are common) it might not be an improvement.
		 */
		while (DUK_LIKELY(p <= p_end_safe)) {
			duk_int_t t1, t2;

			/* The lookup byte is intentionally sign extended to
			 * (at least) 32 bits and then ORed.  This ensures
			 * that is at least 1 byte is negative, the highest
			 * bit of the accumulator will be set at the end and
			 * we don't need to check every byte.
			 *
			 * Read all input bytes first before writing output
			 * bytes to minimize aliasing.
			 */
			DUK_DDD(DUK_DDDPRINT("fast loop: p=%p, p_end_safe=%p, p_end=%p",
			                     (const void *) p, (const void *) p_end_safe, (const void *) p_end));

			t1 = (duk_int_t) duk__base64_dectab_fast[p[0]];
			t1 = (duk_int_t) ((duk_uint_t) t1 << 6) | (duk_int_t) duk__base64_dectab_fast[p[1]];
			t1 = (duk_int_t) ((duk_uint_t) t1 << 6) | (duk_int_t) duk__base64_dectab_fast[p[2]];
			t1 = (duk_int_t) ((duk_uint_t) t1 << 6) | (duk_int_t) duk__base64_dectab_fast[p[3]];

			t2 = (duk_int_t) duk__base64_dectab_fast[p[4]];
			t2 = (duk_int_t) ((duk_uint_t) t2 << 6) | (duk_int_t) duk__base64_dectab_fast[p[5]];
			t2 = (duk_int_t) ((duk_uint_t) t2 << 6) | (duk_int_t) duk__base64_dectab_fast[p[6]];
			t2 = (duk_int_t) ((duk_uint_t) t2 << 6) | (duk_int_t) duk__base64_dectab_fast[p[7]];

			q[0] = (duk_uint8_t) (((duk_uint_t) t1 >> 16) & 0xffU);
			q[1] = (duk_uint8_t) (((duk_uint_t) t1 >> 8) & 0xffU);
			q[2] = (duk_uint8_t) ((duk_uint_t) t1 & 0xffU);

			q[3] = (duk_uint8_t) (((duk_uint_t) t2 >> 16) & 0xffU);
			q[4] = (duk_uint8_t) (((duk_uint_t) t2 >> 8) & 0xffU);
			q[5] = (duk_uint8_t) ((duk_uint_t) t2 & 0xffU);

			/* Optimistic check using one branch. */
			if (DUK_LIKELY((t1 | t2) >= 0)) {
				p += 8;
				q += 6;
			} else if (t1 >= 0) {
				DUK_DDD(DUK_DDDPRINT("fast loop first group was clean, second was not, process one slow path group"));
				DUK_ASSERT(t2 < 0);
				p += 4;
				q += 3;
				break;
			} else {
				DUK_DDD(DUK_DDDPRINT("fast loop first group was not clean, second does not matter, process one slow path group"));
				DUK_ASSERT(t1 < 0);
				break;
			}
		}  /* fast path */

		/* Slow path step 1: try to scan a 4-character encoded group,
		 * end-of-input, or start-of-padding.  We exit with:
		 *   1. n_chars == 4: full group, no padding, no end-of-input.
		 *   2. n_chars < 4: partial group (may also be 0), encountered
		 *      padding or end of input.
		 *
		 * The accumulator is initialized to 1; this allows us to detect
		 * a full group by comparing >= 0x1000000 without an extra
		 * counter variable.
		 */
		t = 1UL;
		for (;;) {
			DUK_DDD(DUK_DDDPRINT("slow loop: p=%p, p_end=%p, t=%lu",
			                     (const void *) p, (const void *) p_end, (unsigned long) t));

			if (DUK_LIKELY(p < p_end)) {
				x = duk__base64_dectab_fast[*p++];
				if (DUK_LIKELY(x >= 0)) {
					DUK_ASSERT(x >= 0 && x <= 63);
					t = (t << 6) + (duk_uint_t) x;
					if (t >= 0x1000000UL) {
						break;
					}
				} else if (x == -1) {
					continue;  /* allowed ascii whitespace */
				} else if (x == -2) {
					p--;
					break;  /* start of padding */
				} else {
					DUK_ASSERT(x == -3);
					goto decode_error;
				}
			} else {
				break;  /* end of input */
			}
		}  /* slow path step 1 */

		/* Complete the padding by simulating pad characters,
		 * regardless of actual input padding chars.
		 */
		n_equal = 0;
		while (t < 0x1000000UL) {
			t = (t << 6) + 0U;
			n_equal++;
		}

		/* Slow path step 2: deal with full/partial group, padding,
		 * etc.  Note that for num chars in [0,3] we intentionally emit
		 * 3 bytes but don't step forward that much, buffer space is
		 * guaranteed in setup.
		 *
		 *  num chars:
		 *   0      ####   no output (= step 0)
		 *   1      #===   reject, 6 bits of data
		 *   2      ##==   12 bits of data, output 1 byte (= step 1)
		 *   3      ###=   18 bits of data, output 2 bytes (= step 2)
		 *   4      ####   24 bits of data, output 3 bytes (= step 3)
		 */
		q[0] = (duk_uint8_t) ((t >> 16) & 0xffU);
		q[1] = (duk_uint8_t) ((t >> 8) & 0xffU);
		q[2] = (duk_uint8_t) (t & 0xffU);

		DUK_ASSERT(n_equal <= 4);
		step = duk__base64_decode_nequal_step[n_equal];
		if (DUK_UNLIKELY(step < 0)) {
			goto decode_error;
		}
		q += step;

		/* Slow path step 3: read and ignore padding and whitespace
		 * until (a) next non-padding and non-whitespace character
		 * after which we resume the fast path, or (b) end of input.
		 * This allows us to accept missing, partial, full, and extra
		 * padding cases uniformly.  We also support concatenated
		 * base-64 documents because we resume scanning afterwards.
		 *
		 * Note that to support concatenated documents well, the '='
		 * padding found inside the input must also allow for 'extra'
		 * padding.  For example, 'Zm===' decodes to 'f' and has one
		 * extra padding char.  So, 'Zm===Zm' should decode 'ff', even
		 * though the standard break-up would be 'Zm==' + '=Zm' which
		 * doesn't make sense.
		 *
		 * We also accept prepended padding like '==Zm9', because it
		 * is equivalent to an empty document with extra padding ('==')
		 * followed by a valid document.
		 */

		for (;;) {
			if (DUK_UNLIKELY(p >= p_end)) {
				goto done;
			}
			x = duk__base64_dectab_fast[*p++];
			if (x == -1 || x == -2) {
				;  /* padding or whitespace, keep eating */
			} else {
				p--;
				break;  /* backtrack and go back to fast path, even for -1 */
			}
		}  /* slow path step 3 */
	}  /* outer fast+slow path loop */

 done:
	DUK_DDD(DUK_DDDPRINT("done; p=%p, p_end=%p",
	                     (const void *) p, (const void *) p_end));

	DUK_ASSERT(p == p_end);

	*out_dst_final = q;
	return 1;

 decode_error:
	return 0;
}
#else  /* DUK_USE_BASE64_FASTPATH */
DUK_LOCAL duk_bool_t duk__base64_decode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst, duk_uint8_t **out_dst_final) {
	duk_uint_t t, x;
	duk_int_t y;
	duk_int8_t step;
	const duk_uint8_t *p;
	const duk_uint8_t *p_end;
	duk_uint8_t *q;
	/* 0x09, 0x0a, or 0x0d */
	duk_uint32_t mask_white = (1U << 9) | (1U << 10) | (1U << 13);

	/* 't' tracks progress of the decoded group:
	 *
	 *  t == 1             no valid chars yet
	 *  t >= 0x40          1x6 = 6 bits shifted in
	 *  t >= 0x1000        2x6 = 12 bits shifted in
	 *  t >= 0x40000       3x6 = 18 bits shifted in
	 *  t >= 0x1000000     4x6 = 24 bits shifted in
	 *
	 * By initializing t=1 there's no need for a separate counter for
	 * the number of characters found so far.
	 */
	p = src;
	p_end = src + srclen;
	q = dst;
	t = 1UL;

	for (;;) {
		duk_small_uint_t n_equal;

		DUK_ASSERT(t >= 1U);
		if (p >= p_end) {
			/* End of input: if input exists, treat like
			 * start of padding, finish the block, then
			 * re-enter here to see we're done.
			 */
			if (t == 1U) {
				break;
			} else {
				goto simulate_padding;
			}
		}

		x = *p++;

		if (x >= 0x41U) {
			/* Valid: a-z and A-Z. */
			DUK_ASSERT(x >= 0x41U && x <= 0xffU);
			if (x >= 0x61U && x <= 0x7aU) {
				y = (duk_int_t) x - 0x61 + 26;
			} else if (x <= 0x5aU) {
				y = (duk_int_t) x - 0x41;
			} else {
				goto decode_error;
			}
		} else if (x >= 0x30U) {
			/* Valid: 0-9 and =. */
			DUK_ASSERT(x >= 0x30U && x <= 0x40U);
			if (x <= 0x39U) {
				y = (duk_int_t) x - 0x30 + 52;
			} else if (x == 0x3dU) {
				/* Skip padding and whitespace unless we're in the
				 * middle of a block.  Otherwise complete group by
				 * simulating shifting in the correct padding.
				 */
				if (t == 1U) {
					continue;
				}
				goto simulate_padding;
			} else {
				goto decode_error;
			}
		} else if (x >= 0x20U) {
			/* Valid: +, /, and 0x20 whitespace. */
			DUK_ASSERT(x >= 0x20U && x <= 0x2fU);
			if (x == 0x2bU) {
				y = 62;
			} else if (x == 0x2fU) {
				y = 63;
			} else if (x == 0x20U) {
				continue;
			} else {
				goto decode_error;
			}
		} else {
			/* Valid: whitespace. */
			duk_uint32_t m;
			DUK_ASSERT(x < 0x20U);  /* 0x00 to 0x1f */
			m = (1U << x);
			if (mask_white & m) {
				/* Allow basic ASCII whitespace. */
				continue;
			} else {
				goto decode_error;
			}
		}

		DUK_ASSERT(y >= 0 && y <= 63);
		t = (t << 6) + (duk_uint_t) y;
		if (t < 0x1000000UL) {
			continue;
		}
		/* fall through; no padding will be added */

	 simulate_padding:
		n_equal = 0;
		while (t < 0x1000000UL) {
			t = (t << 6) + 0U;
			n_equal++;
		}

		/* Output 3 bytes from 't' and advance as needed. */
		q[0] = (duk_uint8_t) ((t >> 16) & 0xffU);
		q[1] = (duk_uint8_t) ((t >> 8) & 0xffU);
		q[2] = (duk_uint8_t) (t & 0xffU);

		DUK_ASSERT(n_equal <= 4U);
		step = duk__base64_decode_nequal_step[n_equal];
		if (step < 0) {
			goto decode_error;
		}
		q += step;

		/* Re-enter loop.  The actual padding characters are skipped
		 * by the main loop.  This handles cases like missing, partial,
		 * full, and extra padding, and allows parsing of concatenated
		 * documents (with extra padding) like: Zm===Zm.  Also extra
		 * prepended padding is accepted: ===Zm9v.
		 */
		t = 1U;
	}
	DUK_ASSERT(t == 1UL);

	*out_dst_final = q;
	return 1;

 decode_error:
	return 0;
}
#endif  /* DUK_USE_BASE64_FASTPATH */

DUK_EXTERNAL const char *duk_base64_encode(duk_hthread *thr, duk_idx_t idx) {
	const duk_uint8_t *src;
	duk_size_t srclen;
	duk_size_t dstlen;
	duk_uint8_t *dst;
	const char *ret;

	DUK_ASSERT_API_ENTRY(thr);

	idx = duk_require_normalize_index(thr, idx);
	src = duk__prep_codec_arg(thr, idx, &srclen);
	DUK_ASSERT(src != NULL);

	/* Compute exact output length.  Computation must not wrap; this
	 * limit works for 32-bit size_t:
	 * >>> srclen = 3221225469
	 * >>> '%x' % ((srclen + 2) / 3 * 4)
	 * 'fffffffc'
	 */
	if (srclen > 3221225469UL) {
		goto type_error;
	}
	dstlen = (srclen + 2U) / 3U * 4U;
	dst = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, dstlen);

	duk__base64_encode_helper((const duk_uint8_t *) src, srclen, dst);

	ret = duk_buffer_to_string(thr, -1);  /* Safe, result is ASCII. */
	duk_replace(thr, idx);
	return ret;

 type_error:
	DUK_ERROR_TYPE(thr, DUK_STR_BASE64_ENCODE_FAILED);
	DUK_WO_NORETURN(return NULL;);
}

DUK_EXTERNAL void duk_base64_decode(duk_hthread *thr, duk_idx_t idx) {
	const duk_uint8_t *src;
	duk_size_t srclen;
	duk_size_t dstlen;
	duk_uint8_t *dst;
	duk_uint8_t *dst_final;

	DUK_ASSERT_API_ENTRY(thr);

	idx = duk_require_normalize_index(thr, idx);
	src = duk__prep_codec_arg(thr, idx, &srclen);
	DUK_ASSERT(src != NULL);

	/* Round up and add safety margin.  Avoid addition before division to
	 * avoid possibility of wrapping.  Margin includes +3 for rounding up,
	 * and +3 for one extra group: the decoder may emit and then backtrack
	 * a full group (3 bytes) from zero-sized input for technical reasons.
	 * Similarly, 'xx' may ecause 1+3 = bytes to be emitted and then
	 * backtracked.
	 */
	dstlen = (srclen / 4) * 3 + 6;  /* upper limit, assuming no whitespace etc */
	dst = (duk_uint8_t *) duk_push_dynamic_buffer(thr, dstlen);
	/* Note: for dstlen=0, dst may be NULL */

	if (!duk__base64_decode_helper((const duk_uint8_t *) src, srclen, dst, &dst_final)) {
		goto type_error;
	}

	/* XXX: convert to fixed buffer? */
	(void) duk_resize_buffer(thr, -1, (duk_size_t) (dst_final - dst));
	duk_replace(thr, idx);
	return;

 type_error:
	DUK_ERROR_TYPE(thr, DUK_STR_BASE64_DECODE_FAILED);
	DUK_WO_NORETURN(return;);
}
#else  /* DUK_USE_BASE64_SUPPORT */
DUK_EXTERNAL const char *duk_base64_encode(duk_hthread *thr, duk_idx_t idx) {
	DUK_UNREF(idx);
	DUK_ERROR_UNSUPPORTED(thr);
	DUK_WO_NORETURN(return NULL;);
}

DUK_EXTERNAL void duk_base64_decode(duk_hthread *thr, duk_idx_t idx) {
	DUK_UNREF(idx);
	DUK_ERROR_UNSUPPORTED(thr);
	DUK_WO_NORETURN(return;);
}
#endif  /* DUK_USE_BASE64_SUPPORT */

/*
 *  Hex
 */

#if defined(DUK_USE_HEX_SUPPORT)
DUK_EXTERNAL const char *duk_hex_encode(duk_hthread *thr, duk_idx_t idx) {
	const duk_uint8_t *inp;
	duk_size_t len;
	duk_size_t i;
	duk_uint8_t *buf;
	const char *ret;
#if defined(DUK_USE_HEX_FASTPATH)
	duk_size_t len_safe;
	duk_uint16_t *p16;
#endif

	DUK_ASSERT_API_ENTRY(thr);

	idx = duk_require_normalize_index(thr, idx);
	inp = duk__prep_codec_arg(thr, idx, &len);
	DUK_ASSERT(inp != NULL);

	/* Fixed buffer, no zeroing because we'll fill all the data. */
	buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, len * 2);
	DUK_ASSERT(buf != NULL);

#if defined(DUK_USE_HEX_FASTPATH)
	DUK_ASSERT((((duk_size_t) buf) & 0x01U) == 0);   /* pointer is aligned, guaranteed for fixed buffer */
	p16 = (duk_uint16_t *) (void *) buf;
	len_safe = len & ~0x03U;
	for (i = 0; i < len_safe; i += 4) {
		p16[0] = duk_hex_enctab[inp[i]];
		p16[1] = duk_hex_enctab[inp[i + 1]];
		p16[2] = duk_hex_enctab[inp[i + 2]];
		p16[3] = duk_hex_enctab[inp[i + 3]];
		p16 += 4;
	}
	for (; i < len; i++) {
		*p16++ = duk_hex_enctab[inp[i]];
	}
#else  /* DUK_USE_HEX_FASTPATH */
	for (i = 0; i < len; i++) {
		duk_small_uint_t t;
		t = (duk_small_uint_t) inp[i];
		buf[i*2 + 0] = duk_lc_digits[t >> 4];
		buf[i*2 + 1] = duk_lc_digits[t & 0x0f];
	}
#endif  /* DUK_USE_HEX_FASTPATH */

	/* XXX: Using a string return value forces a string intern which is
	 * not always necessary.  As a rough performance measure, hex encode
	 * time for tests/perf/test-hex-encode.js dropped from ~35s to ~15s
	 * without string coercion.  Change to returning a buffer and let the
	 * caller coerce to string if necessary?
	 */

	ret = duk_buffer_to_string(thr, -1);  /* Safe, result is ASCII. */
	duk_replace(thr, idx);
	return ret;
}

DUK_EXTERNAL void duk_hex_decode(duk_hthread *thr, duk_idx_t idx) {
	const duk_uint8_t *inp;
	duk_size_t len;
	duk_size_t i;
	duk_int_t t;
	duk_uint8_t *buf;
#if defined(DUK_USE_HEX_FASTPATH)
	duk_int_t chk;
	duk_uint8_t *p;
	duk_size_t len_safe;
#endif

	DUK_ASSERT_API_ENTRY(thr);

	idx = duk_require_normalize_index(thr, idx);
	inp = duk__prep_codec_arg(thr, idx, &len);
	DUK_ASSERT(inp != NULL);

	if (len & 0x01) {
		goto type_error;
	}

	/* Fixed buffer, no zeroing because we'll fill all the data. */
	buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, len / 2);
	DUK_ASSERT(buf != NULL);

#if defined(DUK_USE_HEX_FASTPATH)
	p = buf;
	len_safe = len & ~0x07U;
	for (i = 0; i < len_safe; i += 8) {
		t = ((duk_int_t) duk_hex_dectab_shift4[inp[i]]) |
		    ((duk_int_t) duk_hex_dectab[inp[i + 1]]);
		chk = t;
		p[0] = (duk_uint8_t) t;
		t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 2]]) |
		    ((duk_int_t) duk_hex_dectab[inp[i + 3]]);
		chk |= t;
		p[1] = (duk_uint8_t) t;
		t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 4]]) |
		    ((duk_int_t) duk_hex_dectab[inp[i + 5]]);
		chk |= t;
		p[2] = (duk_uint8_t) t;
		t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 6]]) |
		    ((duk_int_t) duk_hex_dectab[inp[i + 7]]);
		chk |= t;
		p[3] = (duk_uint8_t) t;
		p += 4;

		/* Check if any lookup above had a negative result. */
		if (DUK_UNLIKELY(chk < 0)) {
			goto type_error;
		}
	}
	for (; i < len; i += 2) {
		/* First cast to duk_int_t to sign extend, second cast to
		 * duk_uint_t to avoid signed left shift, and final cast to
		 * duk_int_t result type.
		 */
		t = (duk_int_t) ((((duk_uint_t) (duk_int_t) duk_hex_dectab[inp[i]]) << 4U) |
		                 ((duk_uint_t) (duk_int_t) duk_hex_dectab[inp[i + 1]]));
		if (DUK_UNLIKELY(t < 0)) {
			goto type_error;
		}
		*p++ = (duk_uint8_t) t;
	}
#else  /* DUK_USE_HEX_FASTPATH */
	for (i = 0; i < len; i += 2) {
		/* For invalid characters the value -1 gets extended to
		 * at least 16 bits.  If either nybble is invalid, the
		 * resulting 't' will be < 0.
		 */
		t = (duk_int_t) ((((duk_uint_t) (duk_int_t) duk_hex_dectab[inp[i]]) << 4U) |
		                 ((duk_uint_t) (duk_int_t) duk_hex_dectab[inp[i + 1]]));
		if (DUK_UNLIKELY(t < 0)) {
			goto type_error;
		}
		buf[i >> 1] = (duk_uint8_t) t;
	}
#endif  /* DUK_USE_HEX_FASTPATH */

	duk_replace(thr, idx);
	return;

 type_error:
	DUK_ERROR_TYPE(thr, DUK_STR_HEX_DECODE_FAILED);
	DUK_WO_NORETURN(return;);
}
#else  /* DUK_USE_HEX_SUPPORT */
DUK_EXTERNAL const char *duk_hex_encode(duk_hthread *thr, duk_idx_t idx) {
	DUK_UNREF(idx);
	DUK_ERROR_UNSUPPORTED(thr);
	DUK_WO_NORETURN(return NULL;);
}
DUK_EXTERNAL void duk_hex_decode(duk_hthread *thr, duk_idx_t idx) {
	DUK_UNREF(idx);
	DUK_ERROR_UNSUPPORTED(thr);
	DUK_WO_NORETURN(return;);
}
#endif  /* DUK_USE_HEX_SUPPORT */

/*
 *  JSON
 */

#if defined(DUK_USE_JSON_SUPPORT)
DUK_EXTERNAL const char *duk_json_encode(duk_hthread *thr, duk_idx_t idx) {
#if defined(DUK_USE_ASSERTIONS)
	duk_idx_t top_at_entry;
#endif
	const char *ret;

	DUK_ASSERT_API_ENTRY(thr);
#if defined(DUK_USE_ASSERTIONS)
	top_at_entry = duk_get_top(thr);
#endif

	idx = duk_require_normalize_index(thr, idx);
	duk_bi_json_stringify_helper(thr,
	                             idx /*idx_value*/,
	                             DUK_INVALID_INDEX /*idx_replacer*/,
	                             DUK_INVALID_INDEX /*idx_space*/,
	                             0 /*flags*/);
	DUK_ASSERT(duk_is_string(thr, -1));
	duk_replace(thr, idx);
	ret = duk_get_string(thr, idx);

	DUK_ASSERT(duk_get_top(thr) == top_at_entry);

	return ret;
}

DUK_EXTERNAL void duk_json_decode(duk_hthread *thr, duk_idx_t idx) {
#if defined(DUK_USE_ASSERTIONS)
	duk_idx_t top_at_entry;
#endif

	DUK_ASSERT_API_ENTRY(thr);
#if defined(DUK_USE_ASSERTIONS)
	top_at_entry = duk_get_top(thr);
#endif

	idx = duk_require_normalize_index(thr, idx);
	duk_bi_json_parse_helper(thr,
	                         idx /*idx_value*/,
	                         DUK_INVALID_INDEX /*idx_reviver*/,
	                         0 /*flags*/);
	duk_replace(thr, idx);

	DUK_ASSERT(duk_get_top(thr) == top_at_entry);
}
#else  /* DUK_USE_JSON_SUPPORT */
DUK_EXTERNAL const char *duk_json_encode(duk_hthread *thr, duk_idx_t idx) {
	DUK_ASSERT_API_ENTRY(thr);
	DUK_UNREF(idx);
	DUK_ERROR_UNSUPPORTED(thr);
	DUK_WO_NORETURN(return NULL;);
}

DUK_EXTERNAL void duk_json_decode(duk_hthread *thr, duk_idx_t idx) {
	DUK_ASSERT_API_ENTRY(thr);
	DUK_UNREF(idx);
	DUK_ERROR_UNSUPPORTED(thr);
	DUK_WO_NORETURN(return;);
}
#endif  /* DUK_USE_JSON_SUPPORT */
