aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <nhofmeyr@sysmocom.de>2022-01-12 02:57:58 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2022-06-16 13:04:33 +0200
commitf842c8c8d0d809b298668c9f547e6df837532fce (patch)
tree6c3995481acf761db5785eb40eca18a2ce3927c8
parent2100097ef1be98a338b583f15bb5f529690829ff (diff)
libosmo-gtlv: add auto dec/enc to/from structs
Add osmo_gtlv_coding: describe the value part of a TLV (decode and encode), describe a struct with its members, and get/put readily decoded structs from/to a raw PDU, directly. With osmo_gtlv_coding defined for a protocol's tags, we only deal with encoded PDUs or fully decoded C structs, no TLV related re-implementations clutter up the message handling code. A usage example is given in gtlv_dec_enc_test. The first real use will be the PFCP protocol in osmo-upf.git. With osmo_gtlv_coding, there still is a lot of monkey work involved in describing the decoded structs. A subsequent patch adds a generator for osmo_gtlv_coding and message structs from tag value lists. Related: SYS#5599 Change-Id: I65de793105882a452124ee58adb0e58469e6e796
-rw-r--r--include/osmocom/gtlv/Makefile.am1
-rw-r--r--include/osmocom/gtlv/gtlv_dec_enc.h201
-rw-r--r--src/libosmo-gtlv/Makefile.am1
-rw-r--r--src/libosmo-gtlv/gtlv_dec_enc.c517
-rw-r--r--tests/libosmo-gtlv/Makefile.am12
-rw-r--r--tests/libosmo-gtlv/gtlv_dec_enc_test.c422
-rw-r--r--tests/libosmo-gtlv/gtlv_dec_enc_test.ok128
-rw-r--r--tests/testsuite.at6
8 files changed, 1288 insertions, 0 deletions
diff --git a/include/osmocom/gtlv/Makefile.am b/include/osmocom/gtlv/Makefile.am
index f922ab8..a7c10a6 100644
--- a/include/osmocom/gtlv/Makefile.am
+++ b/include/osmocom/gtlv/Makefile.am
@@ -1,5 +1,6 @@
tlv_HEADERS = \
gtlv.h \
+ gtlv_dec_enc.h \
$(NULL)
tlvdir = $(includedir)/osmocom/gtlv
diff --git a/include/osmocom/gtlv/gtlv_dec_enc.h b/include/osmocom/gtlv/gtlv_dec_enc.h
new file mode 100644
index 0000000..b861129
--- /dev/null
+++ b/include/osmocom/gtlv/gtlv_dec_enc.h
@@ -0,0 +1,201 @@
+/* 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 value */
+ unsigned int tag;
+
+ /*! 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, 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, 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, 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, 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;
+}
diff --git a/src/libosmo-gtlv/Makefile.am b/src/libosmo-gtlv/Makefile.am
index 7ebfb60..bcaff33 100644
--- a/src/libosmo-gtlv/Makefile.am
+++ b/src/libosmo-gtlv/Makefile.am
@@ -22,4 +22,5 @@ noinst_LIBRARIES = \
libosmo_gtlv_a_SOURCES = \
gtlv.c \
+ gtlv_dec_enc.c \
$(NULL)
diff --git a/src/libosmo-gtlv/gtlv_dec_enc.c b/src/libosmo-gtlv/gtlv_dec_enc.c
new file mode 100644
index 0000000..28ee493
--- /dev/null
+++ b/src/libosmo-gtlv/gtlv_dec_enc.c
@@ -0,0 +1,517 @@
+/*
+ * (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/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gtlv/gtlv_dec_enc.h>
+
+/* Reverse offsetof(): return the address of the struct member for a given osmo_gtlv_msg and member ofs_foo value. */
+#define MEMB(M, MEMB_OFS) ((void *)((char *)(M) + (MEMB_OFS)))
+
+#define RETURN_ERROR(RC, IEI, FMT, ARGS...) \
+ do {\
+ if (err_cb) \
+ err_cb(err_cb_data, (void *)decoded_struct, __FILE__, __LINE__, \
+ "IE '%s' (0x%x): " FMT " (%d: %s)\n", \
+ get_value_string(iei_strs, IEI), IEI, ##ARGS, RC, strerror((RC) > 0 ? (RC) : -(RC))); \
+ return RC; \
+ } while (0)
+
+
+/*! Decode a TLV structure from raw data to a decoded struct, for unordered TLV IEs.
+ * How to decode IE values and where to place them in the decoded struct, is defined by ie_coding, an array terminated
+ * by a '{}' entry.
+ * The IEs may appear in any ordering in the TLV data.
+ * For unordered decoding, only IEs with has_presence_flag == true or has_count == true may repeat. Other IE definitions
+ * cause the last read TLV to overwrite all previous decodings, all into the first occurrence in ie_coding.
+ * \param[out] decoded_struct Pointer to the struct to write parsed IE data to.
+ * \param[in] obj_ofs Pass as zero. Used for nested IEs: offset added to decoded_struct to get to a sub-struct.
+ * \param[in] gtlv TLV data to parse, as given in gtlv->msg.*. Must be ready for osmo_gtlv_load_start().
+ * \param[in] ie_coding A list of permitted/expected IEI tags and instructions for decoding.
+ * \param[in] err_cb Function to call to report an error message, or NULL.
+ * \param[in] err_cb_data Caller supplied context to pass to the err_cb as 'data' argument.
+ * \param[in] iei_strs value_string array to give IEI names in error messages passed to err_cb(), or NULL.
+ * \return 0 on success, negative on error.
+ */
+static int osmo_gtlvs_decode_unordered(void *decoded_struct, unsigned int obj_ofs, struct osmo_gtlv_load *gtlv,
+ const struct osmo_gtlv_coding *ie_coding,
+ osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs)
+{
+ void *obj = MEMB(decoded_struct, obj_ofs);
+ const struct osmo_gtlv_coding *iec;
+ unsigned int *multi_count_p;
+
+ /* To check for presence of mandatory IEs, need to keep a flag stack of seen ie_coding entries. This array has
+ * to have at least the nr of entries that the ie_coding array has. Let's allow up to this many ie_coding
+ * entries to avoid dynamic allocation. Seems like enough. */
+ bool seen_ie_coding_entries[4096] = {};
+ bool *seen_p;
+#define CHECK_SEEN(IEC) do { \
+ unsigned int ie_coding_idx = (IEC) - ie_coding; \
+ if (ie_coding_idx >= ARRAY_SIZE(seen_ie_coding_entries)) \
+ RETURN_ERROR(-ENOTSUP, gtlv->tag, \
+ "Too many IE definitions for decoding an unordered TLV structure"); \
+ seen_p = &seen_ie_coding_entries[ie_coding_idx]; \
+ } while (0)
+
+
+ osmo_gtlv_load_start(gtlv);
+
+ /* IEs are allowed to come in any order. So traverse the TLV structure once, and find an IE parser for each (if
+ * any). */
+ for (;;) {
+ int rc;
+ bool *presence_flag_p;
+ unsigned int memb_next_array_idx;
+ unsigned int memb_ofs;
+ unsigned int ie_max_allowed_count;
+
+ rc = osmo_gtlv_load_next(gtlv);
+ if (rc)
+ RETURN_ERROR(rc, gtlv->tag, "Decoding IEs failed on or after this tag");
+ if (!gtlv->val) {
+ /* End of the TLV structure */
+ break;
+ }
+
+ /* ie_max_allowed_count counts how often the same IEI may appear in a message until all struct members
+ * that can store them are filled up. */
+ ie_max_allowed_count = 0;
+
+ do {
+ /* Find the IE coding for this tag */
+ for (iec = ie_coding; !osmo_gtlv_coding_end(iec) && iec->tag != gtlv->tag; iec++);
+ /* No such IE coding found. */
+ if (osmo_gtlv_coding_end(iec))
+ break;
+
+ /* Keep track how often this tag can occur */
+ ie_max_allowed_count += iec->has_count ? iec->count_max : 1;
+
+ /* Was this iec instance already decoded? Then skip to the next one, if any. */
+ presence_flag_p = iec->has_presence_flag ? MEMB(obj, iec->presence_flag_ofs) : NULL;
+ multi_count_p = iec->has_count ? MEMB(obj, iec->count_ofs) : NULL;
+ if ((presence_flag_p && *presence_flag_p)
+ || (multi_count_p && *multi_count_p >= iec->count_max))
+ continue;
+ /* For IEs with a presence flag or a multi count, the decoded struct provides the information
+ * whether the IE has already been decoded. Do the same for mandatory IEs, using local state in
+ * seen_ie_coding_entries[]. */
+ CHECK_SEEN(iec);
+ if (*seen_p)
+ continue;
+ } while (0);
+ if (osmo_gtlv_coding_end(iec)) {
+ if (ie_max_allowed_count) {
+ /* There have been IE definitions for this IEI, but all slots to decode it are already
+ * filled. */
+ RETURN_ERROR(-ENOTSUP, gtlv->tag, "Only %u instances of this IE are supported per message",
+ ie_max_allowed_count);
+ }
+ /* No such IE defined in ie_coding, just skip the TLV. */
+ continue;
+ }
+
+ /* If this is a repeated IE, decode into the correct array index memb[idx],
+ * next idx == (*multi_count_p). We've already guaranteed above that *multi_count_p < count_max. */
+ memb_next_array_idx = multi_count_p ? *multi_count_p : 0;
+ memb_ofs = iec->memb_ofs + memb_next_array_idx * iec->memb_array_pitch;
+
+ /* Decode IE value part */
+ if (iec->nested_ies) {
+ /* A nested IE: the value part of this TLV is in turn a TLV structure. Decode the inner
+ * IEs. */
+ struct osmo_gtlv_load inner_tlv = {
+ .cfg = iec->nested_ies_cfg ? : gtlv->cfg,
+ .src = {
+ .data = gtlv->val,
+ .len = gtlv->len,
+ }
+ };
+ bool ordered;
+ switch (iec->nested_ies_ordered) {
+ case OSMO_GTLV_NESTED_IES_ORDERED:
+ ordered = true;
+ break;
+ case OSMO_GTLV_NESTED_IES_ORDERING_SAME:
+ case OSMO_GTLV_NESTED_IES_UNORDERED:
+ ordered = false;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ rc = osmo_gtlvs_decode(decoded_struct, obj_ofs + memb_ofs, &inner_tlv, ordered, iec->nested_ies,
+ err_cb, err_cb_data, iei_strs);
+ if (rc)
+ RETURN_ERROR(rc, gtlv->tag, "Error while decoding TLV structure nested inside this IE");
+ } else {
+ /* Normal IE, decode the specific IE data. */
+ if (!iec->dec_func)
+ RETURN_ERROR(-EIO, gtlv->tag, "IE definition lacks a dec_func()");
+ rc = iec->dec_func(decoded_struct, MEMB(obj, memb_ofs), gtlv);
+ if (rc)
+ RETURN_ERROR(rc, gtlv->tag, "Error while decoding this IE");
+ }
+
+ if (multi_count_p) {
+ /* A repeated IE, record that we've added one entry. This increments the foo_count value in the
+ * decoded osmo_gtlv_msg.ies.*.
+ * For example, multi_count_p points at osmo_gtlv_msg_session_est_req.create_pdr_count,
+ * and memb_ofs points at osmo_gtlv_msg_session_est_req.create_pdr. */
+ (*multi_count_p)++;
+ }
+ if (presence_flag_p) {
+ *presence_flag_p = true;
+ }
+ CHECK_SEEN(iec);
+ *seen_p = true;
+ }
+
+ /* Check presence of mandatory IEs */
+ for (iec = ie_coding; !osmo_gtlv_coding_end(iec); iec++) {
+ if (iec->has_presence_flag)
+ continue;
+ multi_count_p = iec->has_count ? MEMB(obj, iec->count_ofs) : NULL;
+ if (multi_count_p) {
+ if (*multi_count_p < iec->count_mandatory)
+ RETURN_ERROR(-EINVAL, iec->tag, "Error while decoding: %u instances of this IE are mandatory, got %u",
+ iec->count_mandatory, *multi_count_p);
+ continue;
+ }
+ /* Neither an optional nor a multi member, hence it must be mandatory. */
+ CHECK_SEEN(iec);
+ if (!*seen_p)
+ RETURN_ERROR(-EINVAL, iec->tag, "Missing mandatory IE");
+ }
+ return 0;
+}
+
+/*! Decode a TLV structure from raw data to a decoded struct, for ordered TLV IEs.
+ * How to decode IE values and where to place them in the decoded struct, is defined by ie_coding, an array terminated
+ * by a '{}' entry.
+ * The IEs in the TLV structure must appear in the same order as they are defined in ie_coding.
+ * cause the last read TLV to overwrite all previous decodings, all into the first occurrence in ie_coding.
+ * \param[out] decoded_struct Pointer to the struct to write parsed IE data to.
+ * \param[in] obj_ofs Pass as zero. Used for nested IEs: offset added to decoded_struct to get to a sub-struct.
+ * \param[in] gtlv TLV data to parse, as given in gtlv->msg.*. Must be ready for osmo_gtlv_load_start().
+ * \param[in] ie_coding A list of permitted/expected IEI tags and instructions for decoding.
+ * \param[in] err_cb Function to call to report an error message, or NULL.
+ * \param[in] err_cb_data Caller supplied context to pass to the err_cb as 'data' argument.
+ * \param[in] iei_strs value_string array to give IEI names in error messages passed to err_cb(), or NULL.
+ * \return 0 on success, negative on error.
+ */
+static int osmo_gtlvs_decode_ordered(void *decoded_struct, unsigned int obj_ofs, struct osmo_gtlv_load *gtlv,
+ const struct osmo_gtlv_coding *ie_coding,
+ osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs)
+{
+ void *obj = MEMB(decoded_struct, obj_ofs);
+
+ osmo_gtlv_load_start(gtlv);
+
+ for (; !osmo_gtlv_coding_end(ie_coding); ie_coding++) {
+ int rc;
+ bool *presence_flag = ie_coding->has_presence_flag ? MEMB(obj, ie_coding->presence_flag_ofs) : NULL;
+ unsigned int *multi_count = ie_coding->has_count ? MEMB(obj, ie_coding->count_ofs) : NULL;
+
+ rc = osmo_gtlv_load_next_by_tag(gtlv, ie_coding->tag);
+ switch (rc) {
+ case 0:
+ break;
+ case -ENOENT:
+ if (!presence_flag && (!multi_count || *multi_count < ie_coding->count_mandatory))
+ RETURN_ERROR(rc, ie_coding->tag, "Missing mandatory IE");
+ if (presence_flag)
+ *presence_flag = false;
+ continue;
+ default:
+ RETURN_ERROR(rc, ie_coding->tag, "Error in TLV structure");
+ }
+
+ for (;;) {
+ /* If this is a repeated IE, decode into the correct array index memb[idx],
+ * next idx == (*multi_count) */
+ unsigned int memb_next_array_idx = multi_count ? *multi_count : 0;
+ unsigned int memb_ofs = ie_coding->memb_ofs + memb_next_array_idx * ie_coding->memb_array_pitch;
+
+ if (multi_count && memb_next_array_idx >= ie_coding->count_max)
+ RETURN_ERROR(-ENOTSUP, ie_coding->tag, "Only %u instances of this IE are supported per message",
+ ie_coding->count_max);
+
+ /* Decode IE value part */
+ if (ie_coding->nested_ies) {
+ /* A nested IE: the value part of this TLV is in turn a TLV structure. Decode the inner
+ * IEs. */
+ struct osmo_gtlv_load inner_tlv = {
+ .cfg = ie_coding->nested_ies_cfg ? : gtlv->cfg,
+ .src = {
+ .data = gtlv->val,
+ .len = gtlv->len,
+ }
+ };
+ bool ordered;
+ switch (ie_coding->nested_ies_ordered) {
+ case OSMO_GTLV_NESTED_IES_ORDERING_SAME:
+ case OSMO_GTLV_NESTED_IES_ORDERED:
+ ordered = true;
+ break;
+ case OSMO_GTLV_NESTED_IES_UNORDERED:
+ ordered = false;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ rc = osmo_gtlvs_decode(decoded_struct, obj_ofs + memb_ofs, &inner_tlv, ordered,
+ ie_coding->nested_ies, err_cb, err_cb_data, iei_strs);
+ if (rc)
+ RETURN_ERROR(rc, ie_coding->tag,
+ "Error while decoding TLV structure nested inside this IE");
+ } else {
+ /* Normal IE, decode the specific IE data. */
+ if (!ie_coding->dec_func)
+ RETURN_ERROR(-EIO, ie_coding->tag, "IE definition lacks a dec_func()");
+ rc = ie_coding->dec_func(decoded_struct, MEMB(obj, memb_ofs), gtlv);
+ if (rc)
+ RETURN_ERROR(rc, ie_coding->tag, "Error while decoding this IE");
+ }
+
+ if (presence_flag)
+ *presence_flag = true;
+
+ if (!multi_count) {
+ /* Not a repeated IE. */
+ break;
+ }
+
+ /* A repeated IE, record that we've added one entry. This increments the foo_count value in the
+ * decoded osmo_pfcp_msg.ies.*.
+ * For example, multi_count points at osmo_pfcp_msg_session_est_req.create_pdr_count,
+ * and memb_ofs points at osmo_pfcp_msg_session_est_req.create_pdr. */
+ (*multi_count)++;
+
+ /* Does another one of these IEs follow? */
+ if (osmo_gtlv_load_peek_tag(gtlv) != gtlv->tag) {
+ /* Next tag is a different IE, end the repetition. */
+ break;
+ }
+
+ /* continue, parsing the next repetition of this tag. */
+ rc = osmo_gtlv_load_next(gtlv);
+ if (rc)
+ return rc;
+ }
+ /* continue parsing the next tag. */
+ }
+ return 0;
+}
+
+/*! Decode an entire TLV message from raw data to decoded struct.
+ * How to decode IE values and where to put them in the decoded struct is defined by ie_coding, an array terminated by
+ * a '{}' entry.
+ * \param[out] decoded_struct Pointer to the struct to write parsed IE data to.
+ * \param[in] obj_ofs Pass as zero. Used for nested IEs: offset added to decoded_struct to get to a sub-struct.
+ * \param[in] gtlv TLV data to parse, as given in gtlv->msg.*. Must be ready for osmo_gtlv_load_start().
+ * \param[in] ie_coding A list of permitted/expected IEI tags and instructions for decoding.
+ * \param[in] err_cb Function to call to report an error message, or NULL.
+ * \param[in] err_cb_data Caller supplied context to pass to the err_cb as 'data' argument.
+ * \param[in] iei_strs value_string array to give IEI names in error messages passed to err_cb(), or NULL.
+ * \return 0 on success, negative on error.
+ */
+int osmo_gtlvs_decode(void *decoded_struct, 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)
+{
+ if (!ie_coding)
+ return -ENOTSUP;
+ if (tlv_ordered)
+ return osmo_gtlvs_decode_ordered(decoded_struct, obj_ofs, gtlv, ie_coding, err_cb, err_cb_data, iei_strs);
+ else
+ return osmo_gtlvs_decode_unordered(decoded_struct, obj_ofs, gtlv, ie_coding, err_cb, err_cb_data,
+ iei_strs);
+}
+
+/*! Encode a TLV structure from decoded struct to raw data.
+ * How to encode IE values and where to read them in the decoded struct is defined by ie_coding, an array terminated by
+ * a '{}' entry.
+ * The IEs will be encoded in the order they appear in ie_coding.
+ * \param[out] gtlv Write data using this TLV definition to gtlv->dst.
+ * \param[in] decoded_struct C struct data to encode.
+ * \param[in] obj_ofs Nesting offset, pass as 0.
+ * \param[in] ie_coding A {} terminated list of IEI tags to encode (if present) and instructions for encoding.
+ * \param[in] err_cb Function to call to report an error message, or NULL.
+ * \param[in] err_cb_data Caller supplied context to pass to the err_cb as 'data' argument.
+ * \param[in] iei_strs value_string array to give IEI names in error messages passed to err_cb(), or NULL.
+ * \return 0 on success, negative on error.
+ */
+int osmo_gtlvs_encode(struct osmo_gtlv_put *gtlv, const void *decoded_struct, 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)
+{
+ void *obj = MEMB(decoded_struct, obj_ofs);
+
+ if (!ie_coding)
+ return -ENOTSUP;
+
+ for (; !osmo_gtlv_coding_end(ie_coding); ie_coding++) {
+ int rc;
+ bool *presence_flag_p = ie_coding->has_presence_flag ? MEMB(obj, ie_coding->presence_flag_ofs) : NULL;
+ unsigned int *multi_count_p = ie_coding->has_count ? MEMB(obj, ie_coding->count_ofs) : NULL;
+ unsigned int n;
+ unsigned int i;
+
+ if (presence_flag_p && !*presence_flag_p)
+ continue;
+
+ if (multi_count_p) {
+ n = *multi_count_p;
+ if (!ie_coding->memb_array_pitch)
+ RETURN_ERROR(-EFAULT, ie_coding->tag,
+ "Error in protocol definition: The ie_coding lacks a memb_array_pitch"
+ " value, cannot be used as multi-IE\n");
+ } else {
+ n = 1;
+ }
+
+ for (i = 0; i < n; i++) {
+ unsigned int memb_ofs;
+
+ osmo_gtlv_put_tl(gtlv, ie_coding->tag, 0);
+
+ /* If this is a repeated IE, encode from the correct array index */
+ if (multi_count_p && i >= ie_coding->count_max)
+ RETURN_ERROR(-ENOTSUP, ie_coding->tag,
+ "Only %u instances of this IE are supported per message", ie_coding->count_max);
+ memb_ofs = ie_coding->memb_ofs + i * ie_coding->memb_array_pitch;
+
+ if (ie_coding->nested_ies) {
+ struct osmo_gtlv_put nested_tlv = {
+ .cfg = ie_coding->nested_ies_cfg ? : gtlv->cfg,
+ .dst = gtlv->dst,
+ };
+ rc = osmo_gtlvs_encode(&nested_tlv, decoded_struct, obj_ofs + memb_ofs,
+ ie_coding->nested_ies, err_cb, err_cb_data, iei_strs);
+ if (rc)
+ RETURN_ERROR(rc, ie_coding->tag,
+ "Error while encoding TLV structure nested inside this IE");
+ } else {
+ rc = ie_coding->enc_func(gtlv, decoded_struct, MEMB(obj, memb_ofs));
+ if (rc)
+ RETURN_ERROR(rc, ie_coding->tag, "Error while encoding this IE");
+ }
+
+ osmo_gtlv_put_update_tl(gtlv);
+ }
+ }
+ return 0;
+}
+
+/*! Compose a human readable string describing a decoded struct.
+ * How to encode IE values and where to read them in the decoded struct is defined by ie_coding, an array terminated by
+ * a '{}' entry.
+ * The IEs will be encoded in the order they appear in ie_coding.
+ * \param[out] buf Return the string in this buffer.
+ * \param[in] buflen Size of buf.
+ * \param[in] decoded_struct C struct data to encode.
+ * \param[in] obj_ofs Nesting offset, pass as 0.
+ * \param[in] ie_coding A {} terminated list of IEI tags to encode (if present) and instructions for encoding.
+ * \param[in] iei_strs value_string array to give IEI names in tag headers, or NULL.
+ * \return number of characters that would be written if the buffer is large enough, like snprintf().
+ */
+int osmo_gtlvs_encode_to_str_buf(char *buf, size_t buflen, const void *decoded_struct, unsigned int obj_ofs,
+ const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+
+ void *obj = MEMB(decoded_struct, obj_ofs);
+
+ if (!ie_coding)
+ return -ENOTSUP;
+
+ for (; !osmo_gtlv_coding_end(ie_coding); ie_coding++) {
+ bool *presence_flag_p = ie_coding->has_presence_flag ? MEMB(obj, ie_coding->presence_flag_ofs) : NULL;
+ unsigned int *multi_count_p = ie_coding->has_count ? MEMB(obj, ie_coding->count_ofs) : NULL;
+ unsigned int n;
+ unsigned int i;
+
+ if (presence_flag_p && !*presence_flag_p)
+ continue;
+
+ if (multi_count_p) {
+ n = *multi_count_p;
+ } else {
+ n = 1;
+ }
+
+ if (!n)
+ continue;
+
+ OSMO_STRBUF_PRINTF(sb, " '%s'=", get_value_string(iei_strs, ie_coding->tag));
+ if (multi_count_p)
+ OSMO_STRBUF_PRINTF(sb, "{ ");
+
+ for (i = 0; i < n; i++) {
+ unsigned int memb_ofs;
+
+ /* If this is a repeated IE, encode from the correct array index */
+ if (multi_count_p && i >= ie_coding->count_max)
+ return -ENOTSUP;
+ if (i > 0)
+ OSMO_STRBUF_PRINTF(sb, ", ");
+
+ memb_ofs = ie_coding->memb_ofs + i * ie_coding->memb_array_pitch;
+
+ if (ie_coding->nested_ies) {
+ OSMO_STRBUF_PRINTF(sb, "{");
+ OSMO_STRBUF_APPEND(sb, osmo_gtlvs_encode_to_str_buf, decoded_struct, obj_ofs + memb_ofs,
+ ie_coding->nested_ies, iei_strs);
+ OSMO_STRBUF_PRINTF(sb, " }");
+ } else {
+ if (ie_coding->enc_to_str_func)
+ OSMO_STRBUF_APPEND(sb, ie_coding->enc_to_str_func, MEMB(obj, memb_ofs));
+ else
+ OSMO_STRBUF_PRINTF(sb, "(enc_to_str_func==NULL)");
+ }
+ }
+
+ if (multi_count_p)
+ OSMO_STRBUF_PRINTF(sb, " }");
+ }
+ return sb.chars_needed;
+}
+
+/*! Compose a human readable string describing a decoded struct.
+ * Like osmo_gtlvs_encode_to_str_buf() but returns a talloc allocated string.
+ * \param[in] ctx talloc context to allocate from, e.g. OTC_SELECT.
+ * \param[in] decoded_struct C struct data to encode.
+ * \param[in] obj_ofs Nesting offset, pass as 0.
+ * \param[in] ie_coding A {} terminated list of IEI tags to encode (if present) and instructions for encoding.
+ * \param[in] iei_strs value_string array to give IEI names in tag headers, or NULL.
+ * \return human readable string.
+ */
+char *osmo_gtlvs_encode_to_str_c(void *ctx, const void *decoded_struct, unsigned int obj_ofs,
+ const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs)
+{
+ OSMO_NAME_C_IMPL(ctx, 256, "ERROR", osmo_gtlvs_encode_to_str_buf, decoded_struct, obj_ofs, ie_coding, iei_strs)
+}
diff --git a/tests/libosmo-gtlv/Makefile.am b/tests/libosmo-gtlv/Makefile.am
index 7ab4ae0..6d85334 100644
--- a/tests/libosmo-gtlv/Makefile.am
+++ b/tests/libosmo-gtlv/Makefile.am
@@ -10,10 +10,12 @@ AM_CFLAGS = \
noinst_PROGRAMS = \
gtlv_test \
+ gtlv_dec_enc_test \
$(NULL)
EXTRA_DIST = \
gtlv_test.ok \
+ gtlv_dec_enc_test.ok \
$(NULL)
gtlv_test_SOURCES = \
@@ -25,6 +27,16 @@ gtlv_test_LDADD = \
$(LIBOSMOCORE_LIBS) \
$(NULL)
+gtlv_dec_enc_test_SOURCES = \
+ gtlv_dec_enc_test.c \
+ $(NULL)
+
+gtlv_dec_enc_test_LDADD = \
+ $(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.a \
+ $(LIBOSMOCORE_LIBS) \
+ $(NULL)
+
.PHONY: update_exp
update_exp:
$(builddir)/gtlv_test >$(srcdir)/gtlv_test.ok
+ $(builddir)/gtlv_dec_enc_test >$(srcdir)/gtlv_dec_enc_test.ok
diff --git a/tests/libosmo-gtlv/gtlv_dec_enc_test.c b/tests/libosmo-gtlv/gtlv_dec_enc_test.c
new file mode 100644
index 0000000..89b4011
--- /dev/null
+++ b/tests/libosmo-gtlv/gtlv_dec_enc_test.c
@@ -0,0 +1,422 @@
+/*
+ * (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/>.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gtlv/gtlv_dec_enc.h>
+
+void *ctx;
+
+enum tags {
+ TAG_FOO = 1,
+ TAG_BAR,
+ TAG_BAZ,
+ TAG_REPEAT_INT,
+ TAG_REPEAT_STRUCT,
+ TAG_NEST,
+};
+
+const struct value_string tag_names[] = {
+ { TAG_FOO, "FOO" },
+ { TAG_BAR, "BAR" },
+ { TAG_BAZ, "BAZ" },
+ { TAG_REPEAT_INT, "REPEAT_INT" },
+ { TAG_REPEAT_STRUCT, "REPEAT_STRUCT" },
+ { TAG_NEST, "NEST" },
+ {}
+};
+
+struct bar {
+ char str[23];
+};
+
+struct baz {
+ int v_int;
+ bool v_bool;
+};
+
+enum repeat_enum {
+ R_A,
+ R_B,
+ R_C,
+};
+
+const struct value_string repeat_enum_names[] = {
+ OSMO_VALUE_STRING(R_A),
+ OSMO_VALUE_STRING(R_B),
+ OSMO_VALUE_STRING(R_C),
+ {}
+};
+
+struct repeat {
+ int v_int;
+ bool v_bool;
+ enum repeat_enum v_enum;
+};
+
+struct nested_inner_msg {
+ int foo;
+ struct bar bar;
+ struct baz baz;
+};
+
+struct decoded_msg {
+ int foo;
+ struct bar bar;
+
+ bool baz_present;
+ struct baz baz;
+
+ unsigned int repeat_int_count;
+ int repeat_int[32];
+
+ unsigned int repeat_struct_count;
+ struct repeat repeat_struct[32];
+
+ bool nest_present;
+ struct nested_inner_msg nest;
+};
+
+int dec_u16(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv)
+{
+ int *foo = decode_to;
+ if (gtlv->len != 2)
+ return -EINVAL;
+ *foo = osmo_load16be(gtlv->val);
+ return 0;
+}
+
+int enc_u16(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from)
+{
+ const int *foo = encode_from;
+ if (*foo > INT16_MAX)
+ return -EINVAL;
+ msgb_put_u16(gtlv->dst, *foo);
+ return 0;
+}
+
+int enc_to_str_u16(char *buf, size_t buflen, const void *encode_from)
+{
+ const int *foo = encode_from;
+ return snprintf(buf, buflen, "%d", *foo);
+}
+
+int dec_bar(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv)
+{
+ struct bar *bar = decode_to;
+ if (gtlv->len > sizeof(bar->str) - 1)
+ return -EINVAL;
+ osmo_strlcpy(bar->str, (const char *)gtlv->val, OSMO_MIN(gtlv->len + 1, sizeof(bar->str)));
+ return 0;
+}
+
+int enc_bar(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from)
+{
+ const struct bar *bar = encode_from;
+ int len = strnlen(bar->str, sizeof(bar->str));
+ memcpy(msgb_put(gtlv->dst, len), bar, len);
+ return 0;
+}
+
+int enc_to_str_bar(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct bar *bar = encode_from;
+ return osmo_quote_str_buf3(buf, buflen, bar->str, -1);
+}
+
+int dec_baz(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv)
+{
+ struct baz *baz = decode_to;
+ uint16_t l;
+ if (gtlv->len != 2)
+ return -EINVAL;
+ l = osmo_load16be(gtlv->val);
+ baz->v_int = l & 0x7fff;
+ baz->v_bool = (l & 0x8000) ? true : false;
+ return 0;
+}
+
+int enc_baz(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from)
+{
+ const struct baz *baz = encode_from;
+ if (baz->v_int > 0x7fff)
+ return -EINVAL;
+ msgb_put_u16(gtlv->dst, (baz->v_bool ? 0x8000 : 0) + (baz->v_int & 0x7fff));
+ return 0;
+}
+
+int enc_to_str_baz(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct baz *baz = encode_from;
+ return snprintf(buf, buflen, "{%d,%s}", baz->v_int, baz->v_bool ? "true" : "false");
+}
+
+int dec_repeat_struct(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv)
+{
+ struct repeat *repeat_struct = decode_to;
+ if (gtlv->len != 3)
+ return -EINVAL;
+ repeat_struct->v_int = osmo_load16be(gtlv->val);
+ repeat_struct->v_bool = gtlv->val[2] & 0x80;
+ repeat_struct->v_enum = gtlv->val[2] & 0x7f;
+ return 0;
+}
+
+int enc_repeat_struct(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from)
+{
+ const struct repeat *repeat_struct = encode_from;
+ msgb_put_u16(gtlv->dst, repeat_struct->v_int);
+ msgb_put_u8(gtlv->dst, (repeat_struct->v_bool ? 0x80 : 0) + (repeat_struct->v_enum & 0x7f));
+ return 0;
+}
+
+int enc_to_str_repeat_struct(char *buf, size_t buflen, const void *encode_from)
+{
+ const struct repeat *repeat_struct = encode_from;
+ return snprintf(buf, buflen, "{%d,%s,%s}", repeat_struct->v_int, repeat_struct->v_bool ? "true" : "false",
+ get_value_string(repeat_enum_names, repeat_struct->v_enum));
+}
+
+struct osmo_gtlv_coding nested_inner_msg_ies[] = {
+ {
+ .tag = TAG_FOO,
+ .dec_func = dec_u16,
+ .enc_func = enc_u16,
+ .enc_to_str_func = enc_to_str_u16,
+ .memb_ofs = offsetof(struct nested_inner_msg, foo),
+ },
+ {
+ .tag = TAG_BAR,
+ .dec_func = dec_bar,
+ .enc_func = enc_bar,
+ .enc_to_str_func = enc_to_str_bar,
+ .memb_ofs = offsetof(struct nested_inner_msg, bar),
+ },
+ {
+ .tag = TAG_BAZ,
+ .dec_func = dec_baz,
+ .enc_func = enc_baz,
+ .enc_to_str_func = enc_to_str_baz,
+ .memb_ofs = offsetof(struct nested_inner_msg, baz),
+ },
+ {}
+};
+
+struct osmo_gtlv_coding msg_ie_coding[] = {
+ {
+ .tag = TAG_FOO,
+ .dec_func = dec_u16,
+ .enc_func = enc_u16,
+ .enc_to_str_func = enc_to_str_u16,
+ .memb_ofs = offsetof(struct decoded_msg, foo),
+ },
+ {
+ .tag = TAG_BAR,
+ .dec_func = dec_bar,
+ .enc_func = enc_bar,
+ .enc_to_str_func = enc_to_str_bar,
+ .memb_ofs = offsetof(struct decoded_msg, bar),
+ },
+ {
+ .tag = TAG_BAZ,
+ .dec_func = dec_baz,
+ .enc_func = enc_baz,
+ .enc_to_str_func = enc_to_str_baz,
+ .memb_ofs = offsetof(struct decoded_msg, baz),
+ .has_presence_flag = true,
+ .presence_flag_ofs = offsetof(struct decoded_msg, baz_present),
+ },
+ {
+ .tag = TAG_REPEAT_INT,
+ .dec_func = dec_u16,
+ .enc_func = enc_u16,
+ .enc_to_str_func = enc_to_str_u16,
+ .memb_ofs = offsetof(struct decoded_msg, repeat_int),
+ .memb_array_pitch = OSMO_MEMB_ARRAY_PITCH(struct decoded_msg, repeat_int),
+ .has_count = true,
+ .count_ofs = offsetof(struct decoded_msg, repeat_int_count),
+ .count_max = ARRAY_SIZE(((struct decoded_msg *)0)->repeat_int),
+ },
+ {
+ .tag = TAG_REPEAT_STRUCT,
+ .dec_func = dec_repeat_struct,
+ .enc_func = enc_repeat_struct,
+ .enc_to_str_func = enc_to_str_repeat_struct,
+ .memb_ofs = offsetof(struct decoded_msg, repeat_struct),
+ .memb_array_pitch = OSMO_MEMB_ARRAY_PITCH(struct decoded_msg, repeat_struct),
+ .has_count = true,
+ .count_ofs = offsetof(struct decoded_msg, repeat_struct_count),
+ .count_max = ARRAY_SIZE(((struct decoded_msg *)0)->repeat_struct),
+ },
+ {
+ .tag = TAG_NEST,
+ .memb_ofs = offsetof(struct decoded_msg, nest),
+ .nested_ies = nested_inner_msg_ies,
+ .has_presence_flag = true,
+ .presence_flag_ofs = offsetof(struct decoded_msg, nest_present),
+ },
+ {}
+};
+
+char *decoded_msg_to_str(const struct decoded_msg *m)
+{
+ return osmo_gtlvs_encode_to_str_c(ctx, m, 0, msg_ie_coding, tag_names);
+}
+
+
+const struct decoded_msg enc_dec_tests[] = {
+ {
+ .foo = 23,
+ .bar = { "twentythree" },
+ },
+ {
+ .foo = 23,
+ .bar = { "twentythree" },
+
+ .baz_present = true,
+ .baz = {
+ .v_int = 2323,
+ .v_bool = true,
+ },
+ },
+ {
+ .foo = 23,
+ .bar = { "twentythree" },
+
+ .baz_present = true,
+ .baz = {
+ .v_int = 2323,
+ .v_bool = true,
+ },
+
+ .repeat_int_count = 3,
+ .repeat_int = { 1, 2, 0x7fff },
+ },
+ {
+ .foo = 23,
+ .bar = { "twentythree" },
+
+ .baz_present = true,
+ .baz = {
+ .v_int = 2323,
+ .v_bool = true,
+ },
+
+ .repeat_int_count = 3,
+ .repeat_int = { 1, 2, 0x7fff },
+
+ .repeat_struct_count = 2,
+ .repeat_struct = {
+ {
+ .v_int = 1001,
+ .v_bool = true,
+ .v_enum = R_A,
+ },
+ {
+ .v_int = 1002,
+ .v_bool = false,
+ .v_enum = R_B,
+ },
+ },
+
+ .nest_present = true,
+ .nest = {
+ .foo = 42,
+ .bar = { "fortytwo" },
+ .baz = {
+ .v_int = 4242,
+ .v_bool = false,
+ },
+ },
+ },
+};
+
+static int verify_err_cb_data;
+
+void err_cb(void *data, void *decoded_struct, const char *file, int line, const char *fmt, ...)
+{
+ assert(data == &verify_err_cb_data);
+ va_list args;
+ va_start(args, fmt);
+ //printf("ERR: %s:%d ", file, line);
+ printf("ERR: ");
+ vprintf(fmt, args);
+ va_end(args);
+}
+
+void test_enc_dec(const char *label, const struct osmo_gtlv_cfg *cfg, bool ordered)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(enc_dec_tests); i++) {
+ int rc;
+ const struct decoded_msg *orig = &enc_dec_tests[i];
+ struct decoded_msg parsed = {};
+ struct osmo_gtlv_load load;
+ struct osmo_gtlv_put put;
+
+ printf("\n=== start %s %s[%d]\n", label, __func__, i);
+ printf("encoded: %s\n", decoded_msg_to_str(orig));
+
+ put = (struct osmo_gtlv_put){
+ .cfg = cfg,
+ .dst = msgb_alloc(1024, __func__),
+ };
+ rc = osmo_gtlvs_encode(&put, (void *)orig, 0, msg_ie_coding, err_cb, &verify_err_cb_data, tag_names);
+ printf("osmo_gtlvs_encode() rc = %d\n", rc);
+ printf("%s.\n", osmo_hexdump(put.dst->data, put.dst->len));
+
+ load = (struct osmo_gtlv_load){
+ .cfg = cfg,
+ .src = { put.dst->data, put.dst->len },
+ };
+ rc = osmo_gtlvs_decode(&parsed, 0, &load, ordered, msg_ie_coding, err_cb, &verify_err_cb_data, tag_names);
+ printf("osmo_gtlvs_decode() rc = %d\n", rc);
+ printf("decoded: %s\n", decoded_msg_to_str(&parsed));
+ if (strcmp(decoded_msg_to_str(orig), decoded_msg_to_str(&parsed))) {
+ printf(" ERROR: parsed != orig\n");
+ exit(1);
+ }
+ printf("=== end %s %s[%d]\n", label, __func__, i);
+ }
+}
+
+int main()
+{
+ ctx = talloc_named_const(NULL, 0, "gtlv_test");
+ msgb_talloc_ctx_init(ctx, 0);
+
+ test_enc_dec("t8l8v ordered", &osmo_t8l8v_cfg, true);
+ test_enc_dec("t8l8v unordered", &osmo_t8l8v_cfg, false);
+
+ test_enc_dec("t16l16v ordered", &osmo_t16l16v_cfg, true);
+ test_enc_dec("t16l16v unordered", &osmo_t16l16v_cfg, false);
+
+ talloc_free(ctx);
+ return 0;
+}
diff --git a/tests/libosmo-gtlv/gtlv_dec_enc_test.ok b/tests/libosmo-gtlv/gtlv_dec_enc_test.ok
new file mode 100644
index 0000000..bd6df52
--- /dev/null
+++ b/tests/libosmo-gtlv/gtlv_dec_enc_test.ok
@@ -0,0 +1,128 @@
+
+=== start t8l8v ordered test_enc_dec[0]
+encoded: 'FOO'=23 'BAR'="twentythree"
+osmo_gtlvs_encode() rc = 0
+01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree"
+=== end t8l8v ordered test_enc_dec[0]
+
+=== start t8l8v ordered test_enc_dec[1]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
+osmo_gtlvs_encode() rc = 0
+01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
+=== end t8l8v ordered test_enc_dec[1]
+
+=== start t8l8v ordered test_enc_dec[2]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
+osmo_gtlvs_encode() rc = 0
+01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 04 02 00 01 04 02 00 02 04 02 7f ff .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
+=== end t8l8v ordered test_enc_dec[2]
+
+=== start t8l8v ordered test_enc_dec[3]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
+osmo_gtlvs_encode() rc = 0
+01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 04 02 00 01 04 02 00 02 04 02 7f ff 05 03 03 e9 80 05 03 03 ea 01 06 12 01 02 00 2a 02 08 66 6f 72 74 79 74 77 6f 03 02 10 92 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
+=== end t8l8v ordered test_enc_dec[3]
+
+=== start t8l8v unordered test_enc_dec[0]
+encoded: 'FOO'=23 'BAR'="twentythree"
+osmo_gtlvs_encode() rc = 0
+01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree"
+=== end t8l8v unordered test_enc_dec[0]
+
+=== start t8l8v unordered test_enc_dec[1]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
+osmo_gtlvs_encode() rc = 0
+01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
+=== end t8l8v unordered test_enc_dec[1]
+
+=== start t8l8v unordered test_enc_dec[2]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
+osmo_gtlvs_encode() rc = 0
+01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 04 02 00 01 04 02 00 02 04 02 7f ff .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
+=== end t8l8v unordered test_enc_dec[2]
+
+=== start t8l8v unordered test_enc_dec[3]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
+osmo_gtlvs_encode() rc = 0
+01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 04 02 00 01 04 02 00 02 04 02 7f ff 05 03 03 e9 80 05 03 03 ea 01 06 12 01 02 00 2a 02 08 66 6f 72 74 79 74 77 6f 03 02 10 92 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
+=== end t8l8v unordered test_enc_dec[3]
+
+=== start t16l16v ordered test_enc_dec[0]
+encoded: 'FOO'=23 'BAR'="twentythree"
+osmo_gtlvs_encode() rc = 0
+00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree"
+=== end t16l16v ordered test_enc_dec[0]
+
+=== start t16l16v ordered test_enc_dec[1]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
+osmo_gtlvs_encode() rc = 0
+00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
+=== end t16l16v ordered test_enc_dec[1]
+
+=== start t16l16v ordered test_enc_dec[2]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
+osmo_gtlvs_encode() rc = 0
+00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 00 04 00 02 00 01 00 04 00 02 00 02 00 04 00 02 7f ff .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
+=== end t16l16v ordered test_enc_dec[2]
+
+=== start t16l16v ordered test_enc_dec[3]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
+osmo_gtlvs_encode() rc = 0
+00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 00 04 00 02 00 01 00 04 00 02 00 02 00 04 00 02 7f ff 00 05 00 03 03 e9 80 00 05 00 03 03 ea 01 00 06 00 18 00 01 00 02 00 2a 00 02 00 08 66 6f 72 74 79 74 77 6f 00 03 00 02 10 92 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
+=== end t16l16v ordered test_enc_dec[3]
+
+=== start t16l16v unordered test_enc_dec[0]
+encoded: 'FOO'=23 'BAR'="twentythree"
+osmo_gtlvs_encode() rc = 0
+00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree"
+=== end t16l16v unordered test_enc_dec[0]
+
+=== start t16l16v unordered test_enc_dec[1]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
+osmo_gtlvs_encode() rc = 0
+00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true}
+=== end t16l16v unordered test_enc_dec[1]
+
+=== start t16l16v unordered test_enc_dec[2]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
+osmo_gtlvs_encode() rc = 0
+00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 00 04 00 02 00 01 00 04 00 02 00 02 00 04 00 02 7f ff .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 }
+=== end t16l16v unordered test_enc_dec[2]
+
+=== start t16l16v unordered test_enc_dec[3]
+encoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
+osmo_gtlvs_encode() rc = 0
+00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 00 03 00 02 89 13 00 04 00 02 00 01 00 04 00 02 00 02 00 04 00 02 7f ff 00 05 00 03 03 e9 80 00 05 00 03 03 ea 01 00 06 00 18 00 01 00 02 00 2a 00 02 00 08 66 6f 72 74 79 74 77 6f 00 03 00 02 10 92 .
+osmo_gtlvs_decode() rc = 0
+decoded: 'FOO'=23 'BAR'="twentythree" 'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={ {1001,true,R_A}, {1002,false,R_B} } 'NEST'={ 'FOO'=42 'BAR'="fortytwo" 'BAZ'={4242,false} }
+=== end t16l16v unordered test_enc_dec[3]
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 711f981..788f0f7 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -6,3 +6,9 @@ AT_KEYWORDS([gtlv])
cat $abs_srcdir/libosmo-gtlv/gtlv_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/libosmo-gtlv/gtlv_test], [], [expout], [ignore])
AT_CLEANUP
+
+AT_SETUP([gtlv_dec_enc])
+AT_KEYWORDS([gtlv_dec_enc])
+cat $abs_srcdir/libosmo-gtlv/gtlv_dec_enc_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/libosmo-gtlv/gtlv_dec_enc_test], [], [expout], [ignore])
+AT_CLEANUP