/*
 * Copyright (c) 2021 BayLibre, SAS
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/toolchain.h>
#include <zephyr/linker/utils.h>
#include <zephyr/sys/cbprintf.h>
#include <sys/types.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(cbprintf_package, CONFIG_CBPRINTF_PACKAGE_LOG_LEVEL);

#if defined(CONFIG_CBPRINTF_PACKAGE_SUPPORT_TAGGED_ARGUMENTS) && \
	!Z_C_GENERIC
#error "CONFIG_CBPRINTF_PACKAGE_SUPPORT_TAGGED_ARGUMENTS " \
	"requires toolchain to support _Generic!"
#endif

/**
 * @brief Check if address is in read only section.
 *
 * @param addr Address.
 *
 * @return True if address identified within read only section.
 */
static inline bool ptr_in_rodata(const char *addr)
{
#if defined(CBPRINTF_VIA_UNIT_TEST)
	/* Unit test is X86 (or other host) but not using Zephyr
	 * linker scripts.
	 */
	return false;
#else
	return linker_is_in_rodata(addr);
#endif
}

/*
 * va_list creation
 */

#if defined(__CHECKER__)
static int cbprintf_via_va_list(cbprintf_cb out,
				cbvprintf_external_formatter_func formatter,
				void *ctx,
				const char *fmt, void *buf)
{
	return 0;
}
#elif defined(__aarch64__)
/*
 * Reference:
 *
 * Procedure Call Standard for the ARM 64-bit Architecture
 */

struct __va_list {
	void	*__stack;
	void	*__gr_top;
	void	*__vr_top;
	int	__gr_offs;
	int	__vr_offs;
};

BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
	     "architecture specific support is wrong");

static int cbprintf_via_va_list(cbprintf_cb out,
				cbvprintf_external_formatter_func formatter,
				void *ctx,
				const char *fmt, void *buf)
{
	union {
		va_list ap;
		struct __va_list __ap;
	} u;

	/* create a valid va_list with our buffer */
	u.__ap.__stack = buf;
	u.__ap.__gr_top = NULL;
	u.__ap.__vr_top = NULL;
	u.__ap.__gr_offs = 0;
	u.__ap.__vr_offs = 0;

	return formatter(out, ctx, fmt, u.ap);
}

#elif defined(__x86_64__)
/*
 * Reference:
 *
 * System V Application Binary Interface
 * AMD64 Architecture Processor Supplement
 */

struct __va_list {
	unsigned int gp_offset;
	unsigned int fp_offset;
	void *overflow_arg_area;
	void *reg_save_area;
};

BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
	     "architecture specific support is wrong");

static int cbprintf_via_va_list(cbprintf_cb out,
				cbvprintf_external_formatter_func formatter,
				void *ctx,
				const char *fmt, void *buf)
{
	union {
		va_list ap;
		struct __va_list __ap;
	} u;

	/* create a valid va_list with our buffer */
	u.__ap.overflow_arg_area = buf;
	u.__ap.reg_save_area = NULL;
	u.__ap.gp_offset = (6 * 8);
	u.__ap.fp_offset = (6 * 8 + 16 * 16);

	return formatter(out, ctx, fmt, u.ap);
}

#elif defined(__xtensa__)
/*
 * Reference:
 *
 * gcc source code (gcc/config/xtensa/xtensa.c)
 * xtensa_build_builtin_va_list(), xtensa_va_start(),
 * xtensa_gimplify_va_arg_expr()
 */

struct __va_list {
	void *__va_stk;
	void *__va_reg;
	int __va_ndx;
};

BUILD_ASSERT(sizeof(va_list) == sizeof(struct __va_list),
	     "architecture specific support is wrong");

static int cbprintf_via_va_list(cbprintf_cb out,
				cbvprintf_external_formatter_func formatter,
				void *ctx,
				const char *fmt, void *buf)
{
	union {
		va_list ap;
		struct __va_list __ap;
	} u;

	/* create a valid va_list with our buffer */
	u.__ap.__va_stk = (char *)buf - 32;
	u.__ap.__va_reg = NULL;
	u.__ap.__va_ndx = (6 + 2) * 4;

	return formatter(out, ctx, fmt, u.ap);
}

#else
/*
 * Default implementation shared by many architectures like
 * 32-bit ARM and Intel.
 *
 * We assume va_list is a simple pointer.
 */

BUILD_ASSERT(sizeof(va_list) == sizeof(void *),
	     "architecture specific support is needed");

static int cbprintf_via_va_list(cbprintf_cb out,
				cbvprintf_external_formatter_func formatter,
				void *ctx,
				const char *fmt, void *buf)
{
	union {
		va_list ap;
		void *ptr;
	} u;

	u.ptr = buf;

	return formatter(out, ctx, fmt, u.ap);
}

#endif

static size_t get_package_len(void *packaged)
{
	__ASSERT_NO_MSG(packaged != NULL);

	uint8_t *buf = packaged;
	uint8_t *start = buf;
	unsigned int args_size, s_nbr, ros_nbr;

	args_size = buf[0] * sizeof(int);
	s_nbr     = buf[1];
	ros_nbr   = buf[2];

	/* Move beyond args. */
	buf += args_size;

	/* Move beyond read-only string indexes array. */
	buf += ros_nbr;

	/* Move beyond strings appended to the package. */
	for (unsigned int i = 0; i < s_nbr; i++) {
		buf++;
		buf += strlen((const char *)buf) + 1;
	}

	return (size_t)(uintptr_t)(buf - start);
}

static int append_string(cbprintf_convert_cb cb, void *ctx, const char *str, uint16_t strl)
{
	if (cb == NULL) {
		return 1 + strlen(str);
	}

	strl = strl > 0 ? strl : strlen(str) + 1;
	return cb(str, strl, ctx);
}

int cbvprintf_package(void *packaged, size_t len, uint32_t flags,
		      const char *fmt, va_list ap)
{
/*
 * Internally, a byte is used to store location of a string argument within a
 * package. MSB bit is set if string is read-only so effectively 7 bits are
 * used for index, which should be enough.
 */
#define STR_POS_RO_FLAG BIT(7)
#define STR_POS_MASK BIT_MASK(7)

/* Buffer offset abstraction for better code clarity. */
#define BUF_OFFSET ((uintptr_t)buf - (uintptr_t)buf0)

	uint8_t *buf0 = packaged;  /* buffer start (may be NULL) */
	uint8_t *buf = buf0;       /* current buffer position */
	unsigned int size;         /* current argument's size */
	unsigned int align;        /* current argument's required alignment */
	uint8_t str_ptr_pos[16];   /* string pointer positions */
	uint8_t str_ptr_arg[16];   /* string pointer argument index */
	unsigned int s_idx = 0;    /* index into str_ptr_pos[] */
	unsigned int s_rw_cnt = 0; /* number of rw strings */
	unsigned int s_ro_cnt = 0; /* number of ro strings */
	int arg_idx	      = -1; /* Argument index. Preincremented thus starting from -1.*/
	unsigned int i;
	const char *s;
	bool parsing = false;
	/* Flag indicates that rw strings are stored as array with positions,
	 * instead of appending them to the package.
	 */
	bool rws_pos_en = !!(flags & CBPRINTF_PACKAGE_ADD_RW_STR_POS);
	/* Get number of first read only strings present in the string.
	 * There is always at least 1 (fmt) but flags can indicate more, e.g
	 * fixed prefix appended to all strings.
	 */
	int fros_cnt = 1 + Z_CBPRINTF_PACKAGE_FIRST_RO_STR_CNT_GET(flags);
	bool is_str_arg = false;
	union cbprintf_package_hdr *pkg_hdr = packaged;

	/* Buffer must be aligned at least to size of a pointer. */
	if ((uintptr_t)packaged % sizeof(void *)) {
		return -EFAULT;
	}

#if defined(__xtensa__)
	/* Xtensa requires package to be 16 bytes aligned. */
	if ((uintptr_t)packaged % CBPRINTF_PACKAGE_ALIGNMENT) {
		return -EFAULT;
	}
#endif

	/*
	 * Make room to store the arg list size, the number of
	 * appended writable strings and the number of appended
	 * read-only strings. They both occupy 1 byte each.
	 * Skip a byte. Then a uint32_t to store flags used to
	 * create the package.
	 *
	 * Given the next value to store is the format string pointer
	 * which is guaranteed to be at least 4 bytes, we just reserve
	 * multiple of pointer size for the above to preserve alignment.
	 *
	 * Refer to union cbprintf_package_hdr for more details.
	 */
	buf += sizeof(*pkg_hdr);

	/*
	 * When buf0 is NULL we don't store anything.
	 * Instead we count the needed space to store the data.
	 * In this case, incoming len argument indicates the anticipated
	 * buffer "misalignment" offset.
	 */
	if (buf0 == NULL) {
		buf += len % CBPRINTF_PACKAGE_ALIGNMENT;
		/*
		 * The space to store the data is represented by both the
		 * buffer offset as well as the extra string data to be
		 * appended. When only figuring out the needed space, we
		 * don't append anything. Instead, we reuse the len variable
		 * to sum the size of that data.
		 *
		 * Also, we subtract any initial misalignment offset from
		 * the total as this won't be part of the buffer. To avoid
		 * going negative with an unsigned variable, we add an offset
		 * (CBPRINTF_PACKAGE_ALIGNMENT) that will be removed before
		 * returning.
		 */
		len = CBPRINTF_PACKAGE_ALIGNMENT - (len % CBPRINTF_PACKAGE_ALIGNMENT);
	}

	/*
	 * Otherwise we must ensure we can store at least
	 * the pointer to the format string itself.
	 */
	if (buf0 != NULL && BUF_OFFSET + sizeof(char *) > len) {
		return -ENOSPC;
	}

	/*
	 * Then process the format string itself.
	 * Here we branch directly into the code processing strings
	 * which is in the middle of the following while() loop. That's the
	 * reason for the post-decrement on fmt as it will be incremented
	 * prior to the next (actually first) round of that loop.
	 */
	s = fmt--;
	align = VA_STACK_ALIGN(char *);
	size = sizeof(char *);
	goto process_string;

	while (true) {

#if defined(CONFIG_CBPRINTF_PACKAGE_SUPPORT_TAGGED_ARGUMENTS)
		if ((flags & CBPRINTF_PACKAGE_ARGS_ARE_TAGGED)
		    == CBPRINTF_PACKAGE_ARGS_ARE_TAGGED) {
			int arg_tag = va_arg(ap, int);

			/*
			 * Here we copy the tag over to the package.
			 */
			align = VA_STACK_ALIGN(int);
			size = sizeof(int);

			/* align destination buffer location */
			buf = (void *)ROUND_UP(buf, align);

			/* make sure the data fits */
			if (buf0 != NULL && BUF_OFFSET + size > len) {
				return -ENOSPC;
			}

			if (buf0 != NULL) {
				*(int *)buf = arg_tag;
			}

			buf += sizeof(int);

			if (arg_tag == CBPRINTF_PACKAGE_ARG_TYPE_END) {
				/* End of arguments */
				break;
			}

			/*
			 * There are lots of __fallthrough here since
			 * quite a few of the data types have the same
			 * storage size.
			 */
			switch (arg_tag) {
			case CBPRINTF_PACKAGE_ARG_TYPE_CHAR:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_UNSIGNED_CHAR:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_SHORT:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_UNSIGNED_SHORT:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_INT:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_UNSIGNED_INT:
				align = VA_STACK_ALIGN(int);
				size = sizeof(int);
				break;

			case CBPRINTF_PACKAGE_ARG_TYPE_LONG:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_UNSIGNED_LONG:
				align = VA_STACK_ALIGN(long);
				size = sizeof(long);
				break;

			case CBPRINTF_PACKAGE_ARG_TYPE_LONG_LONG:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_UNSIGNED_LONG_LONG:
				align = VA_STACK_ALIGN(long long);
				size = sizeof(long long);
				break;

			case CBPRINTF_PACKAGE_ARG_TYPE_FLOAT:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_DOUBLE:
				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_LONG_DOUBLE: {
				/*
				 * Handle floats separately as they may be
				 * held in a different register set.
				 */
				union { double d; long double ld; } v;

				if (arg_tag == CBPRINTF_PACKAGE_ARG_TYPE_LONG_DOUBLE) {
					v.ld = va_arg(ap, long double);
					align = VA_STACK_ALIGN(long double);
					size = sizeof(long double);
				} else {
					v.d = va_arg(ap, double);
					align = VA_STACK_ALIGN(double);
					size = sizeof(double);
				}

				/* align destination buffer location */
				buf = (void *) ROUND_UP(buf, align);
				if (buf0 != NULL) {
					/* make sure it fits */
					if (BUF_OFFSET + size > len) {
						return -ENOSPC;
					}
					if (Z_CBPRINTF_VA_STACK_LL_DBL_MEMCPY) {
						memcpy(buf, &v, size);
					} else if (fmt[-1] == 'L') {
						*(long double *)buf = v.ld;
					} else {
						*(double *)buf = v.d;
					}
				}
				buf += size;
				parsing = false;
				continue;
			}

			case CBPRINTF_PACKAGE_ARG_TYPE_PTR_CHAR:
				is_str_arg = true;

				__fallthrough;
			case CBPRINTF_PACKAGE_ARG_TYPE_PTR_VOID:
				align = VA_STACK_ALIGN(void *);
				size = sizeof(void *);
				break;

			default:
				return -EINVAL;
			}

		} else
#endif /* CONFIG_CBPRINTF_PACKAGE_SUPPORT_TAGGED_ARGUMENTS */
		{
			/* Scan the format string */
			if (*++fmt == '\0') {
				break;
			}

			if (!parsing) {
				if (*fmt == '%') {
					parsing = true;
					arg_idx++;
					align = VA_STACK_ALIGN(int);
					size = sizeof(int);
				}
				continue;
			}
			switch (*fmt) {
			case '%':
				parsing = false;
				arg_idx--;
				continue;

			case '#':
			case '-':
			case '+':
			case ' ':
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case '.':
			case 'h':
			case 'l':
			case 'L':
				continue;

			case '*':
				break;

			case 'j':
				align = VA_STACK_ALIGN(intmax_t);
				size = sizeof(intmax_t);
				continue;

			case 'z':
				align = VA_STACK_ALIGN(size_t);
				size = sizeof(size_t);
				continue;

			case 't':
				align = VA_STACK_ALIGN(ptrdiff_t);
				size = sizeof(ptrdiff_t);
				continue;

			case 'c':
			case 'd':
			case 'i':
			case 'o':
			case 'u':
			case 'x':
			case 'X':
				if (fmt[-1] == 'l') {
					if (fmt[-2] == 'l') {
						align = VA_STACK_ALIGN(long long);
						size = sizeof(long long);
					} else {
						align = VA_STACK_ALIGN(long);
						size = sizeof(long);
					}
				}
				parsing = false;
				break;

			case 's':
				is_str_arg = true;

				__fallthrough;
			case 'p':
			case 'n':
				align = VA_STACK_ALIGN(void *);
				size = sizeof(void *);
				parsing = false;
				break;

			case 'a':
			case 'A':
			case 'e':
			case 'E':
			case 'f':
			case 'F':
			case 'g':
			case 'G': {
				/*
				 * Handle floats separately as they may be
				 * held in a different register set.
				 */
				union { double d; long double ld; } v;

				if (fmt[-1] == 'L') {
					v.ld = va_arg(ap, long double);
					align = VA_STACK_ALIGN(long double);
					size = sizeof(long double);
				} else {
					v.d = va_arg(ap, double);
					align = VA_STACK_ALIGN(double);
					size = sizeof(double);
				}
				/* align destination buffer location */
				buf = (void *) ROUND_UP(buf, align);
				if (buf0 != NULL) {
					/* make sure it fits */
					if (BUF_OFFSET + size > len) {
						return -ENOSPC;
					}
					if (Z_CBPRINTF_VA_STACK_LL_DBL_MEMCPY) {
						memcpy(buf, &v, size);
					} else if (fmt[-1] == 'L') {
						*(long double *)buf = v.ld;
					} else {
						*(double *)buf = v.d;
					}
				}
				buf += size;
				parsing = false;
				continue;
			}

			default:
				parsing = false;
				continue;
			}
		}

		/* align destination buffer location */
		buf = (void *) ROUND_UP(buf, align);

		/* make sure the data fits */
		if (buf0 != NULL && BUF_OFFSET + size > len) {
			return -ENOSPC;
		}

		/* copy va_list data over to our buffer */
		if (is_str_arg) {
			s = va_arg(ap, char *);
process_string:
			if (buf0 != NULL) {
				*(const char **)buf = s;
			}

			bool is_ro = (fros_cnt-- > 0) ? true : ptr_in_rodata(s);
			bool do_ro = !!(flags & CBPRINTF_PACKAGE_ADD_RO_STR_POS);

			if (is_ro && !do_ro) {
				/* nothing to do */
			} else {
				uint32_t s_ptr_idx = BUF_OFFSET / sizeof(int);

				/*
				 * In the do_ro case we must consider
				 * room for possible STR_POS_RO_FLAG.
				 * Otherwise the index range is 8 bits
				 * and any overflow is caught later.
				 */
				if (do_ro && s_ptr_idx > STR_POS_MASK) {
					__ASSERT(false, "String with too many arguments");
					return -EINVAL;
				}

				if (s_idx >= ARRAY_SIZE(str_ptr_pos)) {
					__ASSERT(false, "str_ptr_pos[] too small");
					return -EINVAL;
				}

				if (buf0 != NULL) {
					/*
					 * Remember string pointer location.
					 * We will append non-ro strings later.
					 */
					str_ptr_pos[s_idx] = s_ptr_idx;
					str_ptr_arg[s_idx] = arg_idx;
					if (is_ro) {
						/* flag read-only string. */
						str_ptr_pos[s_idx] |= STR_POS_RO_FLAG;
						s_ro_cnt++;
					} else {
						s_rw_cnt++;
					}
				} else if (is_ro) {
					/*
					 * Add only pointer position prefix
					 * when counting strings.
					 */
					len += 1;
				} else if (rws_pos_en) {
					/*
					 * Add only pointer position prefix and
					 * argument index when counting strings.
					 */
					len += 2;
				} else {
					/*
					 * Add the string length, the final '\0'
					 * and size of the pointer position prefix.
					 */
					len += strlen(s) + 1 + 1;
				}

				s_idx++;
			}
			buf += sizeof(char *);

			is_str_arg = false;
		} else if (size == sizeof(int)) {
			int v = va_arg(ap, int);

			if (buf0 != NULL) {
				*(int *)buf = v;
			}
			buf += sizeof(int);
		} else if (size == sizeof(long)) {
			long v = va_arg(ap, long);

			if (buf0 != NULL) {
				*(long *)buf = v;
			}
			buf += sizeof(long);
		} else if (size == sizeof(long long)) {
			long long v = va_arg(ap, long long);

			if (buf0 != NULL) {
				if (Z_CBPRINTF_VA_STACK_LL_DBL_MEMCPY) {
					memcpy(buf, &v, sizeof(long long));
				} else {
					*(long long *)buf = v;
				}
			}
			buf += sizeof(long long);
		} else {
			__ASSERT(false, "unexpected size %u", size);
			return -EINVAL;
		}
	}

	/*
	 * We remember the size of the argument list as a multiple of
	 * sizeof(int) and limit it to a 8-bit field. That means 1020 bytes
	 * worth of va_list, or about 127 arguments on a 64-bit system
	 * (twice that on 32-bit systems). That ought to be good enough.
	 */
	if (BUF_OFFSET / sizeof(int) > 255) {
		__ASSERT(false, "too many format args");
		return -EINVAL;
	}

	/*
	 * If all we wanted was to count required buffer size
	 * then we have it now.
	 */
	if (buf0 == NULL) {
		return BUF_OFFSET + len - CBPRINTF_PACKAGE_ALIGNMENT;
	}

	/* Clear our buffer header. We made room for it initially. */
	*(char **)buf0 = NULL;

	/* Record end of argument list. */
	pkg_hdr->desc.len = BUF_OFFSET / sizeof(int);

	if (rws_pos_en) {
		/* Strings are appended, update location counter. */
		pkg_hdr->desc.str_cnt = 0;
		pkg_hdr->desc.rw_str_cnt = s_rw_cnt;
	} else {
		/* Strings are appended, update append counter. */
		pkg_hdr->desc.str_cnt = s_rw_cnt;
		pkg_hdr->desc.rw_str_cnt = 0;
	}

	pkg_hdr->desc.ro_str_cnt = s_ro_cnt;

#ifdef CONFIG_CBPRINTF_PACKAGE_HEADER_STORE_CREATION_FLAGS
	pkg_hdr->desc.pkg_flags = flags;
#endif

	/* Store strings pointer locations of read only strings. */
	if (s_ro_cnt) {
		for (i = 0; i < s_idx; i++) {
			if (!(str_ptr_pos[i] & STR_POS_RO_FLAG)) {
				continue;
			}

			uint8_t pos = str_ptr_pos[i] & STR_POS_MASK;

			/* make sure it fits */
			if (BUF_OFFSET + 1 > len) {
				return -ENOSPC;
			}
			/* store the pointer position prefix */
			*buf++ = pos;
		}
	}

	/* Store strings prefixed by their pointer location. */
	for (i = 0; i < s_idx; i++) {
		/* Process only RW strings. */
		if (s_ro_cnt && str_ptr_pos[i] & STR_POS_RO_FLAG) {
			continue;
		}

		if (rws_pos_en) {
			size = 0;
			*buf++ = str_ptr_arg[i];
		} else {
			/* retrieve the string pointer */
			s = *(char **)(buf0 + str_ptr_pos[i] * sizeof(int));
			/* clear the in-buffer pointer (less entropy if compressed) */
			*(char **)(buf0 + str_ptr_pos[i] * sizeof(int)) = NULL;
			/* find the string length including terminating '\0' */
			size = strlen(s) + 1;
		}

		/* make sure it fits */
		if (BUF_OFFSET + 1 + size > len) {
			return -ENOSPC;
		}
		/* store the pointer position prefix */
		*buf++ = str_ptr_pos[i];
		/* copy the string with its terminating '\0' */
		memcpy(buf, s, size);
		buf += size;
	}

	/*
	 * TODO: remove pointers for appended strings since they're useless.
	 * TODO: explore leveraging same mechanism to remove alignment padding
	 */

	return BUF_OFFSET;

#undef BUF_OFFSET
#undef STR_POS_RO_FLAG
#undef STR_POS_MASK
}

int cbprintf_package(void *packaged, size_t len, uint32_t flags,
		     const char *format, ...)
{
	va_list ap;
	int ret;

	va_start(ap, format);
	ret = cbvprintf_package(packaged, len, flags, format, ap);
	va_end(ap);
	return ret;
}

int cbpprintf_external(cbprintf_cb out,
		       cbvprintf_external_formatter_func formatter,
		       void *ctx, void *packaged)
{
	uint8_t *buf = packaged;
	struct cbprintf_package_hdr_ext *hdr = packaged;
	char *s, **ps;
	unsigned int i, args_size, s_nbr, ros_nbr, rws_nbr, s_idx;

	if (buf == NULL) {
		return -EINVAL;
	}

	/* Retrieve the size of the arg list and number of strings. */
	args_size = hdr->hdr.desc.len * sizeof(int);
	s_nbr     = hdr->hdr.desc.str_cnt;
	ros_nbr   = hdr->hdr.desc.ro_str_cnt;
	rws_nbr   = hdr->hdr.desc.rw_str_cnt;

	/* Locate the string table */
	s = (char *)(buf + args_size + ros_nbr + 2 * rws_nbr);

	/*
	 * Patch in string pointers.
	 */
	for (i = 0; i < s_nbr; i++) {
		/* Locate pointer location for this string */
		s_idx = *(uint8_t *)s++;
		ps = (char **)(buf + s_idx * sizeof(int));
		/* update the pointer with current string location */
		*ps = s;
		/* move to next string */
		s += strlen(s) + 1;
	}

	/* Skip past the header */
	buf += sizeof(*hdr);

	/* Turn this into a va_list and  print it */
	return cbprintf_via_va_list(out, formatter, ctx, hdr->fmt, buf);
}

/* Function checks if character might be format specifier. Check is relaxed since
 * compiler ensures that correct format specifier is used so it is enough to check
 * that character is not one of potential modifier (e.g. number, dot, etc.).
 */
static bool is_fmt_spec(char c)
{
	return (c >= 64) && (c <= 122);
}

/* Function checks if nth argument is a pointer (%p). Returns true is yes. Returns
 * false if not or if string does not have nth argument.
 */
bool is_ptr(const char *fmt, int n)
{
	char c;
	bool mod = false;
	int cnt = 0;

	while ((c = *fmt++) != '\0') {
		if (mod) {
			if (cnt == n) {
				if (c == 'p') {
					return true;
				} else if (is_fmt_spec(c)) {
					return false;
				}
			} else if (is_fmt_spec(c)) {
				cnt++;
				mod = false;
			}
		}
		if (c == '%') {
			mod = !mod;
		}
	}

	return false;
}

int cbprintf_package_convert(void *in_packaged,
			     size_t in_len,
			     cbprintf_convert_cb cb,
			     void *ctx,
			     uint32_t flags,
			     uint16_t *strl,
			     size_t strl_len)
{
	__ASSERT_NO_MSG(in_packaged != NULL);

	uint8_t *buf = in_packaged;
	uint32_t *buf32 = in_packaged;
	unsigned int args_size, ros_nbr, rws_nbr;
	bool fmt_present = flags & CBPRINTF_PACKAGE_CONVERT_PTR_CHECK ? true : false;
	bool rw_cpy;
	bool ro_cpy;
	struct cbprintf_package_desc *in_desc = in_packaged;

	in_len = in_len != 0 ? in_len : get_package_len(in_packaged);

	/* Get number of RO string indexes in the package and check if copying
	 * includes appending those strings.
	 */
	ros_nbr = in_desc->ro_str_cnt;
	ro_cpy = ros_nbr &&
		(flags & CBPRINTF_PACKAGE_CONVERT_RO_STR) == CBPRINTF_PACKAGE_CONVERT_RO_STR;

	/* Get number of RW string indexes in the package and check if copying
	 * includes appending those strings.
	 */
	rws_nbr = in_desc->rw_str_cnt;
	rw_cpy = rws_nbr > 0 &&
		 (flags & CBPRINTF_PACKAGE_CONVERT_RW_STR) == CBPRINTF_PACKAGE_CONVERT_RW_STR;

	/* If flags are not set or appending request without rw string indexes
	 * present is chosen, just do a simple copy (or length calculation).
	 * Assuming that it is the most common case.
	 */
	if (!rw_cpy && !ro_cpy) {
		if (cb) {
			cb(in_packaged, in_len, ctx);
		}

		return in_len;
	}

	/* If we got here, it means that coping will be more complex and will be
	 * done with strings appending.
	 * Retrieve the size of the arg list.
	 */
	args_size = in_desc->len * sizeof(int);

	int out_len;

	/* Pointer to array with string locations. Array starts with read-only
	 * string locations.
	 */
	const char *fmt = *(const char **)(buf + sizeof(void *));
	uint8_t *str_pos = &buf[args_size];
	size_t strl_cnt = 0;

	/* If null destination, just calculate output length. */
	if (cb == NULL) {
		out_len = (int)in_len;
		if (ro_cpy) {
			for (unsigned int i = 0; i < ros_nbr; i++) {
				const char *str = *(const char **)&buf32[*str_pos];
				int len = append_string(cb, NULL, str, 0);

				/* If possible store calculated string length. */
				if (strl && strl_cnt < strl_len) {
					strl[strl_cnt++] = (uint16_t)len;
				}
				out_len += len;
				str_pos++;
			}
		} else {
			if (ros_nbr && flags & CBPRINTF_PACKAGE_CONVERT_KEEP_RO_STR) {
				str_pos += ros_nbr;
			}
		}

		bool drop_ro_str_pos = !(flags &
					(CBPRINTF_PACKAGE_CONVERT_KEEP_RO_STR |
					 CBPRINTF_PACKAGE_CONVERT_RO_STR));

		/* Handle RW strings. */
		for (unsigned int i = 0; i < rws_nbr; i++) {
			uint8_t arg_idx = *str_pos++;
			uint8_t arg_pos = *str_pos++;
			const char *str = *(const char **)&buf32[arg_pos];
			bool is_ro = ptr_in_rodata(str);
			int len;

			if (fmt_present && is_ptr(fmt, arg_idx)) {
				LOG_WRN("(unsigned) char * used for %%p argument. "
					"It's recommended to cast it to void * because "
					"it may cause misbehavior in certain "
					"configurations. String:\"%s\" argument:%d", fmt, arg_idx);
				/* Since location is being dropped, decrement
				 * output length by 2 (argument index + position)
				 */
				out_len -= 2;
				continue;
			}

			if (is_ro) {
				if (flags & CBPRINTF_PACKAGE_CONVERT_RO_STR) {
					goto calculate_string_length;
				} else {
					out_len -= drop_ro_str_pos ? 2 : 1;
				}
			} else if (flags & CBPRINTF_PACKAGE_CONVERT_RW_STR) {
calculate_string_length:
				len = append_string(cb, NULL, str, 0);

				/* If possible store calculated string length. */
				if (strl && strl_cnt < strl_len) {
					strl[strl_cnt++] = (uint16_t)len;
				}
				/* string length decremented by 1 because argument
				 * index is dropped.
				 */
				out_len += (len - 1);
			}
		}

		return out_len;
	}

	struct cbprintf_package_desc out_desc;
	/* At least one is copied in. */
	uint8_t cpy_str_pos[16];
	/* Up to one will be kept since if both types are kept it returns earlier. */
	uint8_t keep_str_pos[16];
	uint8_t scpy_cnt;
	uint8_t keep_cnt;
	uint8_t *dst;
	int rv;

	/* If read-only strings shall be appended to the output package copy
	 * their indexes to the local array, otherwise indicate that indexes
	 * shall remain in the output package.
	 */
	if (ro_cpy) {
		scpy_cnt = ros_nbr;
		keep_cnt = 0;
		dst = cpy_str_pos;
	} else if (ros_nbr && flags & CBPRINTF_PACKAGE_CONVERT_KEEP_RO_STR) {
		scpy_cnt = 0;
		keep_cnt = ros_nbr;
		dst = keep_str_pos;
	} else {
		scpy_cnt = 0;
		keep_cnt = 0;
		dst = NULL;
	}
	if (dst) {
		memcpy(dst, str_pos, ros_nbr);
	}
	str_pos += ros_nbr;

	/* Go through read-write strings and identify which shall be appended.
	 * Note that there may be read-only strings there. Use address evaluation
	 * to determine if strings is read-only.
	 */
	for (unsigned int i = 0; i < rws_nbr; i++) {
		uint8_t arg_idx = *str_pos++;
		uint8_t arg_pos = *str_pos++;
		const char *str = *(const char **)&buf32[arg_pos];
		bool is_ro = ptr_in_rodata(str);

		if (fmt_present && is_ptr(fmt, arg_idx)) {
			continue;
		}

		if (is_ro) {
			if (flags & CBPRINTF_PACKAGE_CONVERT_RO_STR) {
				__ASSERT_NO_MSG(scpy_cnt < sizeof(cpy_str_pos));
				cpy_str_pos[scpy_cnt++] = arg_pos;
			} else if (flags & CBPRINTF_PACKAGE_CONVERT_KEEP_RO_STR) {
				__ASSERT_NO_MSG(keep_cnt < sizeof(keep_str_pos));
				keep_str_pos[keep_cnt++] = arg_pos;
			} else {
				/* Drop information about ro_str location. */
			}
		} else {
			if (flags & CBPRINTF_PACKAGE_CONVERT_RW_STR) {
				__ASSERT_NO_MSG(scpy_cnt < sizeof(cpy_str_pos));
				cpy_str_pos[scpy_cnt++] = arg_pos;
			} else {
				__ASSERT_NO_MSG(keep_cnt < sizeof(keep_str_pos));
				keep_str_pos[keep_cnt++] = arg_idx;
				keep_str_pos[keep_cnt++] = arg_pos;
			}
		}
	}

	/* Set amount of strings appended to the package. */
	out_desc.len = in_desc->len;
	out_desc.str_cnt = in_desc->str_cnt + scpy_cnt;
	out_desc.rw_str_cnt = (flags & CBPRINTF_PACKAGE_CONVERT_RW_STR) ? 0 : (keep_cnt / 2);
	out_desc.ro_str_cnt = (flags & CBPRINTF_PACKAGE_CONVERT_RO_STR) ? 0 :
			((flags & CBPRINTF_PACKAGE_CONVERT_KEEP_RO_STR) ? keep_cnt : 0);

	/* Temporary overwrite input descriptor to allow bulk transfer */
	struct cbprintf_package_desc in_desc_backup = *in_desc;
	*in_desc = out_desc;

	/* Copy package header and arguments. */
	rv = cb(in_packaged, args_size, ctx);
	if (rv < 0) {
		return rv;
	}
	out_len = rv;
	/* Restore input descriptor. */
	*in_desc = in_desc_backup;

	/* Copy string positions which are kept. */
	rv = cb(keep_str_pos, keep_cnt, ctx);
	if (rv < 0) {
		return rv;
	}
	out_len += rv;

	/* Copy appended strings from source package to destination. */
	size_t strs_len = in_len - (args_size + ros_nbr + 2 * rws_nbr);

	rv = cb(str_pos, strs_len, ctx);
	if (rv < 0) {
		return rv;
	}
	out_len += rv;

	/* Append strings */
	for (unsigned int i = 0; i < scpy_cnt; i++) {
		uint8_t loc = cpy_str_pos[i];
		const char *str = *(const char **)&buf32[loc];
		uint16_t str_len = strl ? strl[i] : 0;

		rv = cb(&loc, 1, ctx);
		if (rv < 0) {
			return rv;
		}
		out_len += rv;

		rv = append_string(cb, ctx, str, str_len);
		if (rv < 0) {
			return rv;
		}
		out_len += rv;
	}

	/* Empty call (can be interpreted as flushing) */
	(void)cb(NULL, 0, ctx);

	return out_len;
}
