/*
    Copyright (C) 2008-2024, Millistream Market Data <support@millistream.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "common.h"
#include "templates.h"
#include "decode.h"
#include "crypt.h"

static void inflate_mmt (const uint8_t * restrict in, uint8_t * const restrict out)
{
	int i;
	for (i = 0; *in != '\0' && i < 14; in++) {
		if (*in >= 'a') {
			int j = *in - 0x60;

			if (j > 14 - i)
				j = 14 - i;

			switch (j) {
				case 14 : out[i++] = '-'; /* FALLTHRU */
				case 13 : out[i++] = '-'; /* FALLTHRU */
				case 12 : out[i++] = '-'; /* FALLTHRU */
				case 11 : out[i++] = '-'; /* FALLTHRU */
				case 10 : out[i++] = '-'; /* FALLTHRU */
				case  9 : out[i++] = '-'; /* FALLTHRU */
				case  8 : out[i++] = '-'; /* FALLTHRU */
				case  7 : out[i++] = '-'; /* FALLTHRU */
				case  6 : out[i++] = '-'; /* FALLTHRU */
				case  5 : out[i++] = '-'; /* FALLTHRU */
				case  4 : out[i++] = '-'; /* FALLTHRU */
				case  3 : out[i++] = '-'; /* FALLTHRU */
				case  2 : out[i++] = '-'; /* FALLTHRU */
				case  1 : out[i++] = '-';
			}
		} else {
			out[i++] = *in;
		}
	}

	/* make sure that we fill the whole MMT string with blanks if needed */
	if (i < 14) {
		switch (14 - i) {
			case 14 : out[i++] = '-'; /* FALLTHRU */
			case 13 : out[i++] = '-'; /* FALLTHRU */
			case 12 : out[i++] = '-'; /* FALLTHRU */
			case 11 : out[i++] = '-'; /* FALLTHRU */
			case 10 : out[i++] = '-'; /* FALLTHRU */
			case  9 : out[i++] = '-'; /* FALLTHRU */
			case  8 : out[i++] = '-'; /* FALLTHRU */
			case  7 : out[i++] = '-'; /* FALLTHRU */
			case  6 : out[i++] = '-'; /* FALLTHRU */
			case  5 : out[i++] = '-'; /* FALLTHRU */
			case  4 : out[i++] = '-'; /* FALLTHRU */
			case  3 : out[i++] = '-'; /* FALLTHRU */
			case  2 : out[i++] = '-'; /* FALLTHRU */
			case  1 : out[i] = '-';
		}
	}

	out[14] = '\0';
}

int zlib_uncompress (uint8_t * restrict src, uint8_t * restrict dst, unsigned long long * restrict slen, unsigned long long * restrict dlen)
{
	z_stream stream;

	stream.next_in = (Bytef *) src;
	stream.avail_in = (uInt) *slen;

	/* Check for source > 64K on 16-bit machine: */
	if ((uLong)stream.avail_in != *slen)
		return Z_BUF_ERROR;

	stream.next_out = (Bytef *) dst;
	stream.avail_out = (uInt) *dlen;

	/* Check for destination > 64K on 16-bit machine: */
	if ((uLong)stream.avail_out != *dlen)
		return Z_BUF_ERROR;

	stream.zalloc = (alloc_func) 0;
	stream.zfree = (free_func) 0;

	int err = inflateInit (&stream);

	if (err != Z_OK)
		return err;

	err = inflate (&stream, Z_FINISH);

	if (err != Z_STREAM_END) {
		inflateEnd(&stream);

		if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
			return Z_DATA_ERROR;

		return err;
	}

	*slen = stream.total_in;
	*dlen = stream.total_out;
	err = inflateEnd(&stream);

	return err;
}

static int decode_bcd (mdf_t handle)
{
	static const char bcdLUT[14] = "0123456789. -\x00";
	int pos = 0;

	do {
		const uint8_t ch = handle->data[handle->fpos++];

		handle->value[pos++] = bcdLUT[ch >> 4];
		handle->value[pos++] = bcdLUT[ch & 0x0f];

		if ((ch & 0x0f) == 0x0D)
			return 1;

		if ((size_t)(pos + 2) > handle->vlen) {
			void *ptr = realloc (handle->value, handle->vlen + 1024);

			if (ptr == NULL) {
				handle->error = MDF_ERR_NO_MEM;
				return 0;
			}

			handle->value = ptr;
			handle->vlen += 1024;
		}
	} while (handle->fpos < handle->message_end);

	handle->error = MDF_ERR_MSG_OOB;
	return 0;
}

static inline int num_digits (const uint64_t value)
{
	if (value < __UINT64_C(100000)) {
		if (value < __UINT64_C(10)) return 1;
		if (value < __UINT64_C(100)) return 2;
		if (value < __UINT64_C(1000)) return 3;
		if (value < __UINT64_C(10000)) return 4;
		return 5;
	}

	if (value < __UINT64_C(10000000000)) {
		if (value < __UINT64_C(1000000)) return 6;
		if (value < __UINT64_C(10000000)) return 7;
		if (value < __UINT64_C(100000000)) return 8;
		if (value < __UINT64_C(1000000000)) return 9;
		return 10;
	}

	if (value < __UINT64_C(1000000000000000)) {
		if (value < __UINT64_C(100000000000)) return 11;
		if (value < __UINT64_C(1000000000000)) return 12;
		if (value < __UINT64_C(10000000000000)) return 13;
		if (value < __UINT64_C(100000000000000)) return 14;
		return 15;
	}

	if (value < __UINT64_C(10000000000000000)) return 16;
	if (value < __UINT64_C(100000000000000000)) return 17;
	if (value < __UINT64_C(1000000000000000000)) return 18;
	if (value < __UINT64_C(10000000000000000000)) return 19;

	return 20;
}

static const char itoaLUT[201] =
	"0001020304050607080910111213141516171819"
	"2021222324252627282930313233343536373839"
	"4041424344454647484950515253545556575859"
	"6061626364656667686970717273747576777879"
	"8081828384858687888990919293949596979899";

/* u8 to string, does however support 999-0 and not 255-0 */
static inline int u8toa (const unsigned int value, char * const string)
{
	/* remove compiler warning for what happens if we get > 999
	 * by defining that situation as undefined */
#ifdef __GNUC__
	if (value > 999)
		__builtin_unreachable();
#endif
	if (value >= 100) {
		memcpy (string, itoaLUT + ((value / 10) * 2), 2);
		string[2] = '0' + value % 10;
		string[3] = '\0';
		return 3;
	} else if (value >= 10) {
		memcpy (string, itoaLUT + (value * 2), 2);
		string[2] = '\0';
		return 2;
	}

	string[0] = '0' + value;
	string[1] = '\0';
	return 1;
}

/* The u32toa() and u64toa() functions modified from https://github.com/amdn/itoa_ljust/
 * Copyright (c) 2016 Arturo Martin-de-Nicolas
 * arturomdn@gmail.com
 *
 * Released under the BSD 3-Clause License, see Google's original copyright
 * and license below.
 *
 * Protocol Buffers - Google's data interchange format
 * Copyright 2008 Google Inc.  All rights reserved.
 * https://developers.google.com/protocol-buffers/
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

static inline int u32toa (uint32_t value, char *string)
{
	const char *old = string;
	uint32_t d,n;

	if (value >= 100000000) {
		if (value < 100000000 * 10) {
			d = value / 100000000;
			*string++ = '0' + d;
			n = 9;
		} else {
			n = 10;
		}
	} else if (value < 100) {
		if (value < 10) {
			*string++ = '0' + value;
			n = 1;
		} else {
			n = 2;
		}
	} else if (value < 10000) {
		if (value < 100 * 10) {
			d = value / 100;
			*string++ = '0' + d;
			n = 3;
		} else {
			n = 4;
		}
	} else if (value < 1000000) {
		if (value < 10000 * 10) {
			d = value / 10000;
			*string++ = '0' + d;
			n = 5;
		} else {
			n = 6;
		}
	} else if (value < 1000000 * 10) {
		d = value / 1000000;
		*string++ = '0' + d;
		n = 7;
	} else {
		n = 8;
	}

	switch (n) {
		case 10: d  = value / 100000000; memcpy (string, itoaLUT + d * 2, 2); string += 2; /* FALLTHRU */
		case  9: value -= d * 100000000; /* FALLTHRU */
		case  8: d  = value /   1000000; memcpy (string, itoaLUT + d * 2, 2); string += 2; /* FALLTHRU */
		case  7: value -= d *   1000000; /* FALLTHRU */
		case  6: d  = value /     10000; memcpy (string, itoaLUT + d * 2, 2); string += 2; /* FALLTHRU */
		case  5: value -= d *     10000; /* FALLTHRU */
		case  4: d  = value /       100; memcpy (string, itoaLUT + d * 2, 2); string += 2; /* FALLTHRU */
		case  3: value -= d *       100; /* FALLTHRU */
		case  2: memcpy (string, itoaLUT + value * 2, 2); string += 2; /* FALLTHRU */
	}

	*string = '\0';

	return string - old;
}

static inline int u64toa (const uint64_t value, char *string)
{
	uint32_t lower = (uint32_t) (value);

	if (lower == value)
		return u32toa (lower, string);

	uint64_t upper = value / 1000000000;

	int len = u64toa (upper, string);

	lower = value - (upper * 1000000000);
	int d = lower / 100000000;

	string += len;
	*string++ = '0' + d;

	lower -= d * 100000000;
	d  = lower / 1000000;
	memcpy (string, itoaLUT + d * 2, 2); string += 2;

	lower -= d * 1000000;
	d  = lower / 10000;
	memcpy (string, itoaLUT + d * 2, 2); string += 2;

	lower -= d * 10000;
	d  = lower / 100;
	memcpy (string, itoaLUT + d * 2, 2); string += 2;

	lower -= d * 100;
	memcpy (string, itoaLUT + lower * 2, 2); string += 2;
	*string = '\0';

	return len + 9;
}

static int parse_intarray (mdf_t handle, const char prefix)
{
	uint64_t num = 0;
	do {
		if (handle->fpos >= handle->message_end)
			return 0;

		num *= 128;
		num += (handle->data[handle->fpos] & 127) + 1;
	} while ((handle->data[handle->fpos++] & 128) == 0);

	/* safety limit */
	if (num > 1000000) {
		handle->error = MDF_ERR_MSG_OOB;
		return 0;
	}

	size_t len = (size_t) (num * 21 + 2);

	if (len > handle->vlen) {
		free (handle->value);
		handle->value = malloc (len);

		if (handle->value == NULL) {
			handle->vlen = 1024;
			handle->value = malloc (handle->vlen);
			handle->error = MDF_ERR_NO_MEM;
			return 0;
		}

		handle->vlen = len;
	}

	int pos = 0;

	if (prefix != ' ')
		handle->value[pos++] = (uint8_t) prefix;

	for (; num != 0; --num) {
		uint64_t v = 0;

		do {
			if (handle->fpos >= handle->message_end)
				return 0;

			v *= 128;
			v += (handle->data[handle->fpos] & 127) + 1;
		} while ((handle->data[handle->fpos++] & 128) == 0);

		pos += u64toa (v - 1, (char *)(handle->value + pos));
		handle->value[pos++] = ' ';
	}

	/* remove the last space */
	handle->value[pos - 1] = '\0';
	return 1;
}

static void parse_msg_header (const uint8_t * const restrict data, uint32_t * restrict pos, uint64_t * const restrict insref, uint16_t * const restrict mref)
{
	const size_t f_pos = *pos;

	/* the message reference is either 8 or 16 bits */
	if (data[f_pos] & 128) {
		memcpy (mref, data + f_pos + 1, sizeof *mref);
		*mref = le16_to_cpu (*mref);
		*pos += 3;
	} else {
		*mref = data[f_pos + 1];
		*pos += 2;
	}

	/* the instrument reference is 8, 16, 32 or 64 bits */
	switch (data[f_pos] & 96) {
		case 0 : {
			*insref = data[*pos] + ((uint64_t)(data[f_pos] & 31) << 8);
			(*pos)++;
			break;
		}

		case 32 : {
			uint16_t val;
			memcpy (&val, data + *pos, sizeof val);
			*insref = le16_to_cpu (val) + ((uint64_t)(data[f_pos] & 31) << 16) + 8192;
			*pos += 2;
			break;
		}

		case 64 : {
			uint32_t val;
			memcpy (&val, data + *pos, sizeof val);
			*insref = le32_to_cpu (val) + ((uint64_t)(data[f_pos] & 31) << 32) + 2105344;
			*pos += 4;
			break;
		}

		default : {
			memcpy (insref, data + *pos, sizeof *insref);
			*insref = le64_to_cpu (*insref);
			*pos += 8;
			break;
		}
	}
}

LIBMDF_API uint8_t mdf_get_delay (mdf_t handle)
{
	if (handle == NULL || (handle->copt & COPT_RECVDELAY) == 0)
		return 0;

	return handle->oldval;
}

LIBMDF_API uint64_t mdf_get_mclass (mdf_t handle)
{
	if (handle == NULL || handle->template == NULL)
		return MDF_MC_UNDEF;

	return  handle->template->mclass;
}

LIBMDF_API uint8_t *mdf_get_message_ptr (mdf_t handle, const uint8_t **endp)
{
	*endp = handle->data + handle->message_end;
	return handle->data + handle->pos;
}

LIBMDF_API int mdf_get_next_message2 (mdf_t handle, uint16_t * const restrict mref, uint64_t * const restrict insref)
{
	if (handle == NULL)
		return 0;

	handle->error = MDF_ERR_NO_ERROR;

try_again:
	if (handle->message_end != 0) {
		handle->data_pos += handle->message_len;

		/* have we consumed the entire read buffer? */
		if (handle->data_pos >= handle->data_used) {
			handle->data_pos = 0;
			handle->data_used = 0;
			handle->message_len = 0;
			handle->message_end = 0;
			return 0;
		}

		handle->data[handle->message_end] = handle->oldval; /* we zeroed this byte before */

		size_t tlen = 0;
		int pos = 0;
		do {
			tlen *= 128;
			tlen += (handle->data[handle->data_pos + pos] & 127) + 1;
		} while ((handle->data[handle->data_pos + pos++] & 128) == 0 && handle->data_pos + pos < handle->data_used);

		if (pos > 5) {
			mdf_disconnect (handle);
			handle->error = MDF_ERR_MSG_OOB;
			return 0;
		}

		/* we have not yet received a full header */
		if ((handle->data[handle->data_pos + pos - 1] & 128) == 0) {
			if (handle->data_pos != 0) {
				handle->data_used -= handle->data_pos;
				memmove (handle->data, handle->data + handle->data_pos, handle->data_used);
				handle->data_pos = 0;
			}

			handle->message_len = 0;
			handle->message_end = 0;
			return 0;
		}

		handle->message_len = tlen + pos;
		handle->message_end = handle->data_pos + handle->message_len;
		handle->pos = pos;
		handle->fpos = handle->data_pos + pos;

		if ((handle->copt & COPT_RECVDELAY) == COPT_RECVDELAY)
			handle->message_len++;

		if ((handle->crypto.cstate & CSTATE_DECRYPT) == CSTATE_DECRYPT)
			handle->message_len += handle->crypto.digest_length;

		if (handle->message_len + handle->data_pos > handle->data_used) {
			if (handle->data_pos != 0) {
				handle->data_used -= handle->data_pos;
				memmove (handle->data, handle->data + handle->data_pos, handle->data_used);
				handle->data_pos = 0;
			}

			handle->message_len = 0;
			handle->message_end = 0;
			return 0;
		}
	} else if (handle->message_len != 0) {
		handle->message_end = handle->data_pos + handle->message_len;

		if ((handle->copt & COPT_RECVDELAY) == COPT_RECVDELAY)
			handle->message_end--;

		if ((handle->crypto.cstate & CSTATE_DECRYPT) == CSTATE_DECRYPT)
			handle->message_end -= handle->crypto.digest_length;
	} else {
		return 0;
	}

	if ((handle->crypto.cstate & CSTATE_DECRYPT) == CSTATE_DECRYPT) {
		unsigned char md[EVP_MAX_MD_SIZE];
		const uint64_t decrypt_cnt = cpu_to_le64 (handle->crypto.decrypt_cnt);
		const uint32_t tmp_len = cpu_to_le32 (handle->message_len);
		int rc = 1;

#if OPENSSL_VERSION_PREREQ(3,0)
		EVP_MAC_CTX *mctx = EVP_MAC_CTX_dup (handle->crypto.dec_mctx);
		EVP_MAC_update (mctx, (const unsigned char *) &decrypt_cnt, sizeof decrypt_cnt);
		EVP_MAC_update (mctx, (const unsigned char *) &tmp_len, sizeof tmp_len);
#else

		if (EVP_DigestSignInit (handle->crypto.digest_decrypt_ctx, NULL, handle->crypto.digest, NULL, NULL) == 0)
			rc = 0;

		if (EVP_DigestSignUpdate (handle->crypto.digest_decrypt_ctx, (const unsigned char *) &decrypt_cnt, sizeof decrypt_cnt) == 0)
			rc = 0;

		if (EVP_DigestSignUpdate (handle->crypto.digest_decrypt_ctx, (const unsigned char *) &tmp_len, sizeof tmp_len) == 0)
			rc = 0;
#endif
		int outlen;
		if (rc == 0 || EVP_DecryptUpdate (handle->crypto.cipher_decrypt_ctx, handle->data + handle->data_pos + handle->pos, &outlen, handle->data + handle->data_pos + handle->pos, handle->message_len - handle->pos) == 0 || outlen != (int)(handle->message_len - handle->pos)) {
#if OPENSSL_VERSION_PREREQ(3,0)
			EVP_MAC_CTX_free (mctx);
#endif
			mdf_disconnect (handle);
			handle->error = MDF_ERR_AUTHFAIL;
			return 0;
		}
		handle->crypto.decrypt_cnt++;

		size_t siglen = sizeof md;

#if OPENSSL_VERSION_PREREQ(3,0)
		EVP_MAC_update (mctx, handle->data + handle->data_pos, handle->message_len - handle->crypto.digest_length);
		EVP_MAC_final (mctx, md, &siglen, siglen);
		EVP_MAC_CTX_free (mctx);
#else
		if (EVP_DigestSignUpdate (handle->crypto.digest_decrypt_ctx, handle->data + handle->data_pos, handle->message_len - handle->crypto.digest_length) == 0)
			rc = 0;

		if (EVP_DigestSignFinal (handle->crypto.digest_decrypt_ctx, md, &siglen) == 0 || siglen != (size_t)handle->crypto.digest_length)
			rc = 0;
#endif
		if (rc == 0 || memcmp (handle->data + handle->data_pos + handle->message_len - handle->crypto.digest_length, md, handle->crypto.digest_length) != 0) {
			mdf_disconnect (handle);
			handle->error = MDF_ERR_AUTHFAIL;
			return 0;
		}
	}

	/* so we can safely use strlen() & co */
	handle->oldval = handle->data[handle->message_end];
	handle->data[handle->message_end] = '\0';

	handle->pos = handle->fpos;

	parse_msg_header (handle->data, &handle->pos, insref, mref);

	handle->fpos = handle->pos + 1;
	handle->in_pmap_pos = 128;
	handle->field_pmap_pos = 0;

	if (*mref == 0) { /* hardcoded message */
		int expected = 0;
		while (!atomic_compare_exchange_weak_explicit (&handle->slock, &expected, 1, memory_order_acquire, memory_order_relaxed))
			expected = 0;

		if (*insref == 1 || *insref == 2) { /* heartbeat request/response */
			struct timespec ts;
			clock_gettime (CLOCK_REALTIME, &ts);

			const uint64_t curtime = ((uint64_t)ts.tv_sec) * __UINT64_C(1000000000) + (uint64_t) ts.tv_nsec;
			uint64_t rcvtime = curtime;

			if (handle->message_end - handle->pos >= 8) {
				memcpy (&rcvtime, handle->data + handle->pos, sizeof rcvtime);
								rcvtime = le64_to_cpu (rcvtime);
				handle->timediff_ns = rcvtime - curtime;
			}

			/* was heartbeat request */
			if (*insref == 1 && mdf_int_send_heartbeat_response (handle, curtime, rcvtime) == -1) {
				atomic_store_explicit (&handle->slock, 0, memory_order_release);
				return 0;
			} else if (*insref == 2) {
				if (handle->callbacks.func_status != NULL) {
					char timestamp_str[21], timestamp_rcv_str[21];

					u64toa (curtime, timestamp_str);
					u64toa (rcvtime, timestamp_rcv_str);

					handle->callbacks.func_status (handle->callbacks.udata_status, MDF_STATUS_RCV_HB_RES, timestamp_str, timestamp_rcv_str);
				}
			}
		} else if (*insref == 5) { /* new keys from server */
			if (mdf_int_handle_server_rekey (&handle->crypto, handle->data + handle->pos, handle->message_end - handle->pos) == 0) {
				mdf_disconnect (handle);
				handle->error = MDF_ERR_AUTHFAIL;
				atomic_store_explicit (&handle->slock, 0, memory_order_release);
				return 0;
			}
		} else if (*insref == 7) { /* server have entered delay mode */
			handle->copt |= COPT_RECVDELAY;
		}

		atomic_store_explicit (&handle->slock, 0, memory_order_release);
		goto try_again;
	} else if (*mref == MDF_M_MESSAGESREFERENCE + 1) {
		handle->template_version = *insref;
		templates_decode (handle->data + handle->pos, handle->data + handle->message_end, &handle->templates, &handle->templates_old);
	} else if (*mref == MDF_M_LOGONGREETING + 1) {
		int expected = 0;
		while (!atomic_compare_exchange_weak_explicit (&handle->slock, &expected, 1, memory_order_acquire, memory_order_relaxed))
			expected = 0;

		if ((handle->copt & COPT_NOENCRYPTION) == COPT_NOENCRYPTION) {
			if (mdf_int_send_rekey (handle) == 0) {
				handle->error = MDF_ERR_AUTHFAIL;
				atomic_store_explicit (&handle->slock, 0, memory_order_release);
				return 0;
			}
		}

		atomic_store_explicit (&handle->slock, 0, memory_order_release);
	}

	/* mref 0 have no template */
	(*mref)--;

	/* since this check is done after we may have parsed the
	 * msg header we must make sure that the data buffer always
	 * is at least 12 bytes larger than the actual tlen! */
	if (handle->fpos > handle->message_end) {
		mdf_disconnect (handle);
		handle->error = MDF_ERR_MSG_OOB;
		return 0;
	}

	if (*mref >= handle->templates->num)
		handle->template = NULL;
	else
		handle->template = &handle->templates->templates[*mref];

	return 1;
}

LIBMDF_API int mdf_get_next_message (mdf_t handle, int * restrict message_reference, int * restrict message_class, uint64_t * restrict instrument_reference)
{
	uint16_t mref;
	int ret = mdf_get_next_message2 (handle, &mref, instrument_reference);

	*message_reference = mref;

	uint64_t mclass = mdf_get_mclass (handle);

	if (mclass > INT32_MAX)
		*message_class = -1;
	else
		*message_class = (int32_t) mclass;

	return ret;
}

LIBMDF_API int mdf_get_next_field (mdf_t handle, uint32_t * const restrict tag, char ** const restrict value)
{
	if (handle == NULL)
		return 0;

	if (handle->fpos >= handle->message_end)
		return 0;

	/* unknown template */
	if (handle->template == NULL || handle->template->tags == NULL) {
		handle->error = MDF_ERR_UNKNOWN_TEMPLATE;
		return 0;
	}

	/* let's find the first used bit in the pmap */
	while ((handle->data[handle->pos] & handle->in_pmap_pos) == 0) {
		handle->in_pmap_pos /= 2;
		handle->field_pmap_pos++;

		/* no more bits in this pmap, go to next pmap */
		if (handle->in_pmap_pos == 0) {
			handle->pos = handle->fpos;
			handle->fpos++;
			handle->in_pmap_pos = 128;

			if (handle->fpos >= handle->message_end)
				return 0;
		}
	}

	/* our template is out-of-date or we have gotten erreneous input, bail out */
	if (handle->field_pmap_pos >= handle->template->tags_num) {
		handle->error = MDF_ERR_TEMPLATE_OOB;
		return 0;
	}

	*tag = handle->template->tags[handle->field_pmap_pos];
	*value = (char *) handle->value;

set_field_code_again:;
	/* field_codes that are currently not in use: 224 - 239, 243, 245, 254 */
	uint8_t field_code = handle->data[handle->fpos++];

	if (field_code <= 127) {
		if (field_code == 0) {
			u8toa (((unsigned int) handle->data[handle->fpos++]) + 28, *value);
		} else if (field_code > 99) {
			u8toa (field_code - 100, *value);
		} else {
			int pos = u8toa ((unsigned int) handle->data[handle->fpos++], *value);
			(*value)[pos++] = '.';

			if (field_code < 10)
				(*value)[pos++] = '0';

			u8toa (field_code, *value + pos);
		}
	} else if (field_code <= 143) { /* .xxxx */
		uint16_t val;
		memcpy (&val, handle->data + handle->fpos, sizeof val);
		const uint32_t v = le16_to_cpu (val) + ((field_code & 15) << 16) + 1;
		const uint32_t r = v / 10000;
		const uint32_t m = v - (r * 10000);
		int pos = u8toa (r, *value);

		if (m != 0) {
			(*value)[pos++] = '.';

			if (m < 1000) (*value)[pos++] = '0';
			if (m < 100) (*value)[pos++] = '0';
			if (m < 10) (*value)[pos++] = '0';

			u32toa (m, *value + pos);
		}

		handle->fpos += 2;
	} else if (field_code <= 159) { /* 10741.xx */
		uint16_t val;
		memcpy (&val, handle->data + handle->fpos, sizeof val);
		const uint32_t v = le16_to_cpu (val) + ((field_code & 15) << 16) + 25601;
		const uint32_t r = v / 100;
		const uint32_t m = v - (r * 100);
		int pos = u32toa (r, *value);

		if (m != 0) {
			(*value)[pos++] = '.';

			if (m < 10) (*value)[pos++] = '0';

			u8toa (m, *value + pos);
		}

		handle->fpos += 2;
	} else if (field_code <= 175) { /* Positive integer with 1-16 zeroes */
		uint64_t v = 0;
		do {
			if (handle->fpos >= handle->message_end) {
				handle->error = MDF_ERR_MSG_OOB;
				return 0;
			}

			v *= 128;
			v += (handle->data[handle->fpos] & 127) + 1;
		} while ((handle->data[handle->fpos++] & 128) == 0);

		int len = u64toa (v, *value);
		int num = (field_code & 15) + 1;

		memset (*value + len, '0', num);
		(*value)[len + num] = '\0';
	} else if (field_code <= 191) { /* Positive number with 0-15 decimals */
		uint64_t v = 0;
		do {
			if (handle->fpos >= handle->message_end) {
				handle->error = MDF_ERR_MSG_OOB;
				return 0;
			}

			v *= 128;
			v += (handle->data[handle->fpos] & 127) + 1;
		} while ((handle->data[handle->fpos++] & 128) == 0);

		int decimals = field_code & 15;
		if (decimals != 0) {
			const int digits = num_digits (v);

			if (decimals >= digits) {
				const int diff = decimals - digits;

				memcpy (*value, "0.", 2);
				memset (*value + 2, '0', diff);
				u64toa (v, *value + diff + 2);
			} else {
				const int diff = digits - decimals;

				u64toa (v, *value);
				memmove (*value + diff + 1, *value + diff, decimals + 1);
				(*value)[diff] = '.';
			}
		} else {
			u64toa (v, *value);
		}
	} else if (field_code <= 207) { /* Negative number with 0-15 decimals */
		uint64_t v = 0;
		do {
			if (handle->fpos >= handle->message_end) {
				handle->error = MDF_ERR_MSG_OOB;
				return 0;
			}

			v *= 128;
			v += (handle->data[handle->fpos] & 127) + 1;
		} while ((handle->data[handle->fpos++] & 128) == 0);

		int decimals = field_code & 15;
		if (decimals != 0) {
			const int digits = num_digits (v);
			if (decimals >= digits) {
				const int diff = decimals - digits;

				memcpy (*value, "-0.", 3);
				memset (*value + 3, '0', diff);
				u64toa (v, *value + diff + 3);
			} else {
				const int diff = digits - decimals + 1;

				(*value)[0] = '-';
				u64toa (v, *value + 1);
				memmove (*value + diff + 1, *value + diff, decimals + 1);
				(*value)[diff] = '.';
			}
		} else {
			(*value)[0] = '-';
			u64toa (v, *value + 1);
		}
	} else if (field_code <= 223) { /* Date */
		uint16_t val;
		memcpy (&val, handle->data + handle->fpos, sizeof val);
		int days = le16_to_cpu (val) + ((field_code & 15) << 16);
		int years = days / 448; days -= years * 448;
		int months = days / 32; days -= months * 32;

		if (days == 0) {
			if (months == 0) {
				u32toa (years, *value);
			} else {
				int pos = u32toa (years, *value);
				(*value)[pos++] = '-';

				if (months < 10)
					(*value)[pos++] = '0';

				u8toa (months, *value + pos);
			}
		} else if (months == 0) {
			int pos = u32toa (years, *value);
			(*value)[pos++] = '-';

			switch (days) {
				case 1 : memcpy (*value + pos, "H1", 3); break;
				case 2 : memcpy (*value + pos, "H2", 3); break;
				case 3 : memcpy (*value + pos, "T1", 3); break;
				case 4 : memcpy (*value + pos, "T2", 3); break;
				case 5 : memcpy (*value + pos, "T3", 3); break;
				case 6 : memcpy (*value + pos, "Q1", 3); break;
				case 7 : memcpy (*value + pos, "Q2", 3); break;
				case 8 : memcpy (*value + pos, "Q3", 3); break;
				case 9 : memcpy (*value + pos, "Q4", 3); break;
				default: (*value)[pos++] = 'W'; u8toa (days - 9, *value + pos); break;
			}
		} else if (months == 13) {
			int pos = u32toa (years, *value);
			(*value)[pos++] = '-';
			(*value)[pos++] = 'W';
			u8toa (days + 22, *value + pos);
		} else {
			int pos = u32toa (years, *value);

			(*value)[pos++] = '-';

			if (months < 10) (*value)[pos++] = '0';
			pos += u8toa (months, *value + pos);

			(*value)[pos++] = '-';

			if (days < 10) (*value)[pos++] = '0';
			u8toa (days, *value + pos);
		}

		handle->fpos += 2;
	} else if (field_code == 240) { /* NULL */
		*value = NULL;
	} else if (field_code == 241) { /* Uncompressed String */
		const size_t len = strlen ((char *) (handle->data + handle->fpos)) + 1;
		*value = (char *)(handle->data + handle->fpos);

		handle->fpos += len;

		if (*tag == MDF_F_MMT) {
			inflate_mmt ((uint8_t *)*value, handle->value);
			*value = (char *) handle->value;
		}
	} else if (field_code == 242) { /* Compressed String */
		unsigned long long slen, dlen = 0;
		do {
			if (handle->fpos >= handle->message_end) {
				handle->error = MDF_ERR_MSG_OOB;
				return 0;
			}

			dlen *= 128;
			dlen += (handle->data[handle->fpos] & 127) + 1;
		} while ((handle->data[handle->fpos++] & 128) == 0);

		int method = 0;

		if (dlen == 1) { /* zstd */
			dlen = ZSTD_getFrameContentSize (handle->data + handle->fpos, handle->message_end - handle->fpos);
			slen = ZSTD_findFrameCompressedSize (handle->data + handle->fpos, handle->message_end - handle->fpos);
			method = 1;
		} else {
			slen = handle->message_end - handle->fpos;
			method = 0;
		}

		if (dlen + 1 > handle->vlen) {
			free (handle->value);
			handle->value = malloc (dlen + 1);

			if (handle->value == NULL) {
				handle->vlen = 1024;
				handle->value = malloc (handle->vlen);
				handle->error = MDF_ERR_NO_MEM;
				return 0;
			}

			handle->vlen = dlen + 1;
			*value = (char *) handle->value;
		}

		if (method == 0) {
			if (zlib_uncompress (handle->data + handle->fpos, handle->value, &slen, &dlen) != Z_OK) {
				handle->error = MDF_ERR_MSG_OOB;
				return 0;
			}
		} else if (method == 1) {
			dlen = ZSTD_decompress (handle->value, dlen, handle->data + handle->fpos, slen);

			if (ZSTD_isError (dlen)) {
				handle->error = MDF_ERR_MSG_OOB;
				return 0;
			}
		}

		handle->fpos += (uint32_t) slen;
		handle->value[dlen] = '\0';
	} else if (field_code == 244) { /* Time Value Type #3 */
		int pos = 0;

		if ((handle->data[handle->fpos + 3] & 128) == 128) { /* nano seconds */
			uint64_t nsec = ((uint64_t)handle->data[handle->fpos + 5]) | ((uint64_t)handle->data[handle->fpos + 4] << 8) | ((uint64_t)handle->data[handle->fpos + 2] << 16) | ((uint64_t)handle->data[handle->fpos + 1] << 24) | ((uint64_t)handle->data[handle->fpos] << 32) | ((uint64_t)(handle->data[handle->fpos + 3] & 127) << 40);
			handle->fpos += 6;

			const int hours   = nsec / __UINT64_C(3600000000000); nsec -= hours * __UINT64_C(3600000000000);
			const int minutes = nsec / __UINT64_C(60000000000);   nsec -= minutes * __UINT64_C(60000000000);
			const int seconds = nsec / __UINT64_C(1000000000);    nsec -= seconds * __UINT64_C(1000000000);

			if (hours < 10) (*value)[pos++] = '0';
			pos += u8toa (hours, *value + pos);
			(*value)[pos++] = ':';

			if (minutes < 10) (*value)[pos++] = '0';
			pos += u8toa (minutes, *value + pos);
			(*value)[pos++] = ':';

			if (seconds < 10) (*value)[pos++] = '0';
			pos += u8toa (seconds, *value + pos);
			(*value)[pos++] = '.';

			if (nsec < __UINT64_C(100000000)) (*value)[pos++] = '0';
			if (nsec < 10000000) (*value)[pos++] = '0';
			if (nsec < 1000000) (*value)[pos++] = '0';
			if (nsec < 100000) (*value)[pos++] = '0';
			if (nsec < 10000) (*value)[pos++] = '0';
			if (nsec < 1000) (*value)[pos++] = '0';
			if (nsec < 100) (*value)[pos++] = '0';
			if (nsec < 10) (*value)[pos++] = '0';
			u32toa (nsec, *value + pos);
		} else { /* milliseconds */
			uint32_t msec;
			memcpy (&msec, handle->data + handle->fpos, sizeof msec);
			msec = le32_to_cpu (msec);
			handle->fpos += 4;

			const int hours = msec / 3600000; msec -= hours * 3600000;
			const int minutes = msec / 60000; msec -= minutes * 60000;
			const int seconds = msec / 1000; msec -= seconds * 1000;

			if (hours < 10) (*value)[pos++] = '0';
			pos += u8toa (hours, *value + pos);
			(*value)[pos++] = ':';

			if (minutes < 10) (*value)[pos++] = '0';
			pos += u8toa (minutes, *value + pos);
			(*value)[pos++] = ':';

			if (seconds < 10) (*value)[pos++] = '0';
			pos += u8toa (seconds, *value + pos);
			(*value)[pos++] = '.';

			if (msec < 100) (*value)[pos++] = '0';
			if (msec < 10) (*value)[pos++] = '0';
			u8toa (msec, *value + pos);
		}
	} else if (field_code == 246) { /* Time Value Type #1 */
		uint16_t seconds;
		memcpy (&seconds, handle->data + handle->fpos, sizeof seconds);
		seconds = le16_to_cpu (seconds);
		int hours = seconds / 3600; seconds -= hours * 3600;
		int minutes = seconds / 60; seconds -= minutes * 60;

		int pos = 0;
		if (hours < 10) (*value)[pos++] = '0';
		pos += u8toa (hours, *value + pos);
		(*value)[pos++] = ':';

		if (minutes < 10) (*value)[pos++] = '0';
		pos += u8toa (minutes, *value + pos);
		(*value)[pos++] = ':';

		if (seconds < 10) (*value)[pos++] = '0';
		u8toa (seconds, *value + pos);

		handle->fpos += 2;
	} else if (field_code == 247) { /* Time Value Type #2 */
		uint16_t val;
		memcpy (&val, handle->data + handle->fpos, sizeof val);
		int seconds = le16_to_cpu (val) + 65535;
		int hours = seconds / 3600; seconds -= hours * 3600;
		int minutes = seconds / 60; seconds -= minutes * 60;

		int pos = 0;
		if (hours < 10) (*value)[pos++] = '0';
		pos += u8toa (hours, *value + pos);
		(*value)[pos++] = ':';

		if (minutes < 10) (*value)[pos++] = '0';
		pos += u8toa (minutes, *value + pos);
		(*value)[pos++] = ':';

		if (seconds < 10) (*value)[pos++] = '0';
		u8toa (seconds, *value + pos);

		handle->fpos += 2;
	} else if (field_code == 248) { /* IntArray(+) */
		if (!parse_intarray (handle, '+'))
			return 0;

		*value = (char *) handle->value; /* since handle->value could be reallocated */
	} else if (field_code == 249) { /* IntArray(-) */
		if (!parse_intarray (handle, '-'))
			return 0;

		*value = (char *) handle->value; /* since handle->value could be reallocated */
	} else if (field_code == 250) { /* IntArray(=) */
		if (!parse_intarray (handle, *tag != MDF_F_INSREFLIST ? '=' : ' '))
			return 0;

		*value = (char *) handle->value; /* since handle->value could be reallocated */
	} else if (field_code == 251) { /* Tagged field uint8 */
		*tag = handle->data[handle->fpos++];
		handle->in_pmap_pos *= 2;
		handle->field_pmap_pos--;
		goto set_field_code_again;
	} else if (field_code == 252) { /* Tagged field uint16 */
		uint16_t val;
		memcpy (&val, handle->data + handle->fpos, sizeof val);
		*tag = le16_to_cpu (val);
		handle->fpos += 2;
		handle->in_pmap_pos *= 2;
		handle->field_pmap_pos--;
		goto set_field_code_again;
	} else if (field_code == 253) { /* Tagged field uint32 */
		uint32_t val;
		memcpy (&val, handle->data + handle->fpos, sizeof val);
		*tag = le32_to_cpu (val);
		handle->fpos += 4;
		handle->in_pmap_pos *= 2;
		handle->field_pmap_pos--;
		goto set_field_code_again;
	} else if (field_code == 255) { /* Packed BCD */
		if (!decode_bcd (handle))
			return 0;
	}

	/* so we don't loop forever on the same pmap bit on every call to this function */
	handle->in_pmap_pos /= 2;
	handle->field_pmap_pos++;

	/* if no more bits in this pmap, go to next pmap */
	if (handle->in_pmap_pos == 0) {
		handle->pos = handle->fpos;
		handle->fpos++;
		handle->in_pmap_pos = 128;
	}

	return 1;
}

