/* Decode and encode the value parts of a TLV structure */
/*
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 * All Rights Reserved.
 *
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 *
 * SPDX-License-Identifier: GPL-2.0+
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 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 General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#pragma once

#include <stdbool.h>

#include <osmocom/gtlv/gtlv.h>

struct value_string;

/* User defined function to decode a single TLV value part. See struct osmo_gtlv_coding.
 * \param decoded_struct  Pointer to the root struct, as context information, e.g. for logging.
 * \param decode_to  Pointer to the struct member, write the decoded value here.
 * \param gtlv  TLV loader, pointing at a gtlv->val of gtlv->len bytes.
 * \return 0 on success, nonzero on error, e.g. -EINVAL if the gtlv->val is invalid.
 */
typedef int (*osmo_gtlv_dec_func)(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv);

/* User defined function to encode a single TLV value part. See struct osmo_gtlv_coding.
 * \param gtlv  TLV writer, pointing at a gtlv->dst to msgb_put() data in.
 * \param decoded_struct  Pointer to the root struct, as context information, e.g. for logging.
 * \param encode_from  Pointer to the struct member, obtain the value to encode from here.
 * \return 0 on success, nonzero on error, e.g. -EINVAL if encode_from has an un-encodable value.
 */
typedef int (*osmo_gtlv_enc_func)(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from);

/* Optional user defined function to convert a decoded IE struct (the Value part stored as C struct) to string. See
 * struct osmo_gtlv_coding.
 * \param buf  Return string in this buffer.
 * \param buflen  Size of buf.
 * \param str_of  Pointer to the struct member described by an osmo_gtlv_coding, obtain the value to encode from here.
 * \return number of characters that would be written if the buffer is large enough, like snprintf().
 */
typedef int (*osmo_gtlv_enc_to_str_func)(char *buf, size_t buflen, const void *str_of);

/* Whether TLV structures nested inside the value data of an outer IE should be parsed in the same order. */
enum osmo_gtlv_coding_nested_ies_ordered {
	/*! When stepping into nested IEs, keep the same ordering requirement as the outer IE. */
	OSMO_GTLV_NESTED_IES_ORDERING_SAME = 0,
	/*! Require IEs in a PDU to appear exactly in the order defined by osmo_gtlv_coding arrays. Causes a parsing
	 * failure if the TLVs appear in a different order. Does much less iterating looking for matching tags when
	 * decoding (faster). */
	OSMO_GTLV_NESTED_IES_ORDERED,
	/*! Do not require IEs to be in the defined order in decoded PDUs. When encoding a TLV, IEs will always be
	 * encoded in the order they are defined. This has an effect on decoding only. */
	OSMO_GTLV_NESTED_IES_UNORDERED,
};

#define OSMO_ARRAY_PITCH(arr) ((char *)(&(arr)[1]) - (char *)(arr))
#define OSMO_MEMB_ARRAY_PITCH(obj_type, arr_memb) OSMO_ARRAY_PITCH(((obj_type *)0)->arr_memb)

/*! Definition of how to decode/encode a IE to/from a struct.
 * Kept in lists describing TLV structures, and nestable.
 *
 * Instance lists of this can be composed manually, or auto-generated using gtlv_gen.c. Auto-generating has the benefit
 * that the decoded structs to match the IEs are also generated at the same time and thus always match the message
 * definitions. For an example, see tests/libosmo-gtlv/test_gtlv_gen/. */
struct osmo_gtlv_coding {
	/*! the IEI discriminator, and optional instance number */
	struct osmo_gtlv_tag_inst ti;

	/*! Decoding function callback. Invoked for each defined and present IE encountered in the message.
	 * Return 0 on success, negative on failure. */
	osmo_gtlv_dec_func dec_func;
	/*! Encoding function callback. Invoked for each defined and present IE encountered in the message.
	 * Return 0 on success, negative on failure. */
	osmo_gtlv_enc_func enc_func;

	/*! Means to output the decoded value to a human readable string, optional. */
	osmo_gtlv_enc_to_str_func enc_to_str_func;

	/*! offsetof(decoded_struct_type, member_var): how far into the base struct you find a specific field for decoded
	 * value. For example, memb_ofs = offsetof(struct foo_msg, ies.bar_response.cause).
	 * When decoding, the decoded value is written here, when encoding it is read from here. */
	unsigned int memb_ofs;
	/*! For repeated IEs (.has_count = true), the array pitch / the offset to add to get to the next array index. */
	unsigned int memb_array_pitch;

	/*! True for optional/conditional IEs. */
	bool has_presence_flag;
	/* For optional/conditional IEs (has_presence_flag = true), the offset of the bool foo_present flag,
	 * For example, if there are
	 *
	 * struct foo_msg {
	 *         struct baz baz;
	 *         bool baz_present;
	 * };
	 *
	 * then set
	 * memb_ofs = offsetof(struct foo_msg, baz);
	 * has_presence_flag = true;
	 * presence_flag_ofs = offsetof(struct foo_msg, baz_present);
	 */
	unsigned int presence_flag_ofs;

	/*! True for repeated IEs, for array members:
	 *
	 * struct foo_msg {
	 *         struct moo moo[10];
	 *         unsigned int moo_count;
	 * };
	 *
	 * memb_ofs = offsetof(struct foo_msg, moo);
	 * has_count = true;
	 * count_ofs = offsetof(struct foo_msg, moo_count);
	 * count_max = 10;
	 */
	bool has_count;
	/*! For repeated IEs, the offset of the unsigned int foo_count indicator of how many array indexes are
	 * in use. See has_count. */
	unsigned int count_ofs;
	/*! Maximum array size for member_var[]. See has_count. */
	unsigned int count_max;
	/*! If nonzero, it is an error when less than this amount of the repeated IE have been decoded. */
	unsigned int count_mandatory;

	/*! For nested TLVs: if this IE's value part is itself a separate TLV structure, point this at the list of IE
	 * coding definitions for the inner IEs.
	 * In this example, the nested IEs decode/encode to different sub structs depending on the tag value.
	 *
	 *     struct bar {
	 *             int aaa;
	 *             int bbb;
	 *     };
	 *
	 *     struct foo_msg {
	 *             struct bar bar;
	 *             struct bar other_bar;
	 *     };
	 *
	 *     struct osmo_gtlv_coding bar_nested_ies[] = {
	 *             { FOO_IEI_AAA, .memb_ofs = offsetof(struct bar, aaa), },
	 *             { FOO_IEI_BBB, .memb_ofs = offsetof(struct bar, bbb), },
	 *             {}
	 *     };
	 *
	 *     struct osmo_gtlv_coding foo_msg_ies[] = {
	 *             { FOO_IEI_GOO, .memb_ofs = offsetof(struct foo_msg, bar), .nested_ies = bar_nested_ies, },
	 *             { FOO_IEI_OTHER_GOO, .memb_ofs = offsetof(struct foo_msg, other_bar), .nested_ies = bar_nested_ies, },
	 *             {}
	 *     };
	 */
	const struct osmo_gtlv_coding *nested_ies;

	/*! If the nested TLV has a different tag/length size than the outer TLV structure, provide a different config
	 * here. If they are the same, just keep this NULL. */
	const struct osmo_gtlv_cfg *nested_ies_cfg;

	/*! When stepping into nested IEs, what is the ordering requirement for the nested TLV structure? */
	enum osmo_gtlv_coding_nested_ies_ordered nested_ies_ordered;
};


/*! User defined hook for error logging during TLV and value decoding.
 * \param decoded_struct  Pointer to the base struct describing this message, for context.
 * \param file  Source file of where the error occurred.
 * \param line  Source file line of where the error occurred.
 * \param fmt  Error message string format.
 * \param ...  Error message string args.
 */
typedef void (*osmo_gtlv_err_cb)(void *data, void *decoded_struct, const char *file, int line, const char *fmt, ...);

int osmo_gtlvs_decode(void *decoded_struct, size_t decoded_struct_size,
		      unsigned int obj_ofs, struct osmo_gtlv_load *gtlv, bool tlv_ordered,
		      const struct osmo_gtlv_coding *ie_coding,
		      osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs);

int osmo_gtlvs_encode(struct osmo_gtlv_put *gtlv, const void *decoded_struct, size_t decoded_struct_size,
		      unsigned int obj_ofs, const struct osmo_gtlv_coding *ie_coding, osmo_gtlv_err_cb err_cb,
		      void *err_cb_data, const struct value_string *iei_strs);

int osmo_gtlvs_encode_to_str_buf(char *buf, size_t buflen,
				 const void *decoded_struct, size_t decoded_struct_size, unsigned int obj_ofs,
				 const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs);
char *osmo_gtlvs_encode_to_str_c(void *ctx,
				 const void *decoded_struct, size_t decoded_struct_size, unsigned int obj_ofs,
				 const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs);

static inline bool osmo_gtlv_coding_end(const struct osmo_gtlv_coding *iec)
{
	return iec->dec_func == NULL && iec->enc_func == NULL && iec->nested_ies == NULL;
}
