neels has uploaded this change for review. (
https://gerrit.osmocom.org/c/libosmocore/+/28286 )
Change subject: add libosmo-gtlv, moved from osmo-upf.git
......................................................................
add libosmo-gtlv, moved from osmo-upf.git
libosmo-gtlv does not strictly conform to the libosmocore Makefile.am
structure, because it was first added in osmo-upf.git, and because we
use other Makefile.am conventions outside of libosmocore.git:
- use of SUBDIRS instead of single top level Makefile.am
- name of libosmo-gtlv with a dash, other libs here omit a dash.
- name of src dirs being "libosmo-gtlv" instead of just "gtlv".
I hope that code review agrees with this choice of rather keeping the
code conforming to our conventions that we established later, and not
strictly changing to follow "legacy" conventions in libosmocore.git.
Pasting initial libosmo-gtlv commit logs from osmo-upf.git:
(1) TLV skeleton traversal:
An all new TLV parser supporting:
- Any size of T and L (determined by callback function),
- "Grouped IEs", so that an IE payload is a nested IE structure,
- optional/mandatory/multi-occurence IEs,
- decoding unordered tags (or enforcing strict order).
Will be used for PFCP message decoding and encoding, a T16L16V protocol
which requires above features.
Upcoming patches add
- translating PDUs to plain C structs and vice versa
- TLV generator to reduce repetition a in protocol definition
- TLIV capability
Previously, the way we deal with TLVs causes a lot of code
re-implementation: the TL decoding is taken care of by the API, but for
encoding, we essentially re-implement each protocol and each encoded
message in the individual programs. This API is an improvement in that
we only once implement the TL coding (or just use osmo_t8l8v_cfg /
osmo_t16l16v_cfg), get symmetric de- and encoding of the TL, and only
need to deal with the value part of each IE.
The common pattern of
- store TL preliminarily,
- write V data and
- update L after V is complete
is conveniently done by osmo_gtlv_put_update_tl().
(2) Decoding and encoding user data:
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.
(3) TLIV support:
During code review, it was indicated that some TLV protocols that we
will likely deal with in the near future also employ an I, an instance
value of a tag. Add TLIV support.
A usage example for a manually implemented TLIV structure is found in
tests/libosmo-gtlv/gtlv_test.c.
A usage example for a generated TLIV protocol is found in
tests/libosmo-gtlv/test_tliv/.
Related: OS#5599
Related: Id72cdf94da60d4b6d09d0044c74e672c4412c15d (osmo-upf)
Change-Id: I25ab400f0c5707fdc0d8e480aca19871c2e26e71
---
M Makefile.am
M configure.ac
M include/Makefile.am
A include/osmocom/gtlv/gtlv.h
A include/osmocom/gtlv/gtlv_dec_enc.h
A include/osmocom/gtlv/gtlv_gen.h
A libosmo-gtlv.pc.in
A src/libosmo-gtlv/Makefile.am
A src/libosmo-gtlv/gtlv.c
A src/libosmo-gtlv/gtlv_dec_enc.c
A src/libosmo-gtlv/gtlv_gen.c
M tests/Makefile.am
A tests/libosmo-gtlv/Makefile.am
A tests/libosmo-gtlv/gtlv_dec_enc_test.c
A tests/libosmo-gtlv/gtlv_dec_enc_test.ok
A tests/libosmo-gtlv/gtlv_test.c
A tests/libosmo-gtlv/gtlv_test.ok
A tests/libosmo-gtlv/test_gtlv_gen/Makefile.am
A tests/libosmo-gtlv/test_gtlv_gen/gen__myproto_ies_auto.c
A tests/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test.c
A tests/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test.ok
A tests/libosmo-gtlv/test_gtlv_gen/myproto_ies_custom.c
A tests/libosmo-gtlv/test_gtlv_gen/myproto_ies_custom.h
A tests/libosmo-gtlv/test_tliv/Makefile.am
A tests/libosmo-gtlv/test_tliv/gen__myproto_ies_auto.c
A tests/libosmo-gtlv/test_tliv/myproto_ies_custom.c
A tests/libosmo-gtlv/test_tliv/myproto_ies_custom.h
A tests/libosmo-gtlv/test_tliv/tliv_test.c
A tests/libosmo-gtlv/test_tliv/tliv_test.ok
M tests/testsuite.at
30 files changed, 4,781 insertions(+), 2 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/86/28286/1
diff --git a/Makefile.am b/Makefile.am
index b3663ba..8b750ec 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -13,15 +13,18 @@
src/sim \
src/pseudotalloc \
src/usb \
+ src/libosmo-gtlv \
utils \
tapset \
tests \
+ tests/libosmo-gtlv \
$(NULL)
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libosmocore.pc libosmocodec.pc libosmovty.pc libosmogsm.pc \
libosmogb.pc libosmoctrl.pc libosmocoding.pc libosmosim.pc \
- libosmousb.pc
+ libosmousb.pc \
+ libosmo-gtlv.pc
aclocaldir = $(datadir)/aclocal
dist_aclocal_DATA = m4/osmo_ac_code_coverage.m4 \
diff --git a/configure.ac b/configure.ac
index bf85c07..dc3a4d4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -584,6 +584,7 @@
libosmoctrl.pc
libosmosim.pc
libosmousb.pc
+ libosmo-gtlv.pc
include/Makefile
src/Makefile
src/vty/Makefile
@@ -595,9 +596,13 @@
src/gb/Makefile
src/ctrl/Makefile
src/pseudotalloc/Makefile
+ src/libosmo-gtlv/Makefile
tapset/Makefile
tests/Makefile
tests/atlocal
+ tests/libosmo-gtlv/Makefile
+ tests/libosmo-gtlv/test_gtlv_gen/Makefile
+ tests/libosmo-gtlv/test_tliv/Makefile
utils/Makefile
Doxyfile.core
Doxyfile.gsm
diff --git a/include/Makefile.am b/include/Makefile.am
index a234014..9f81eb7 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -164,7 +164,11 @@
osmocom/gsm/sysinfo.h \
osmocom/gsm/tlv.h \
osmocom/sim/class_tables.h \
- osmocom/sim/sim.h
+ osmocom/sim/sim.h \
+ osmocom/gtlv/gtlv.h \
+ osmocom/gtlv/gtlv_dec_enc.h \
+ osmocom/gtlv/gtlv_gen.h \
+ $(NULL)
if ENABLE_PLUGIN
nobase_include_HEADERS += osmocom/core/plugin.h
diff --git a/include/osmocom/gtlv/gtlv.h b/include/osmocom/gtlv/gtlv.h
new file mode 100644
index 0000000..d800f71
--- /dev/null
+++ b/include/osmocom/gtlv/gtlv.h
@@ -0,0 +1,157 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+struct msgb;
+struct osmo_gtlv_load;
+struct osmo_gtlv_put;
+struct value_string;
+
+struct osmo_gtlv_tag_inst {
+ unsigned int tag;
+ bool instance_present;
+ unsigned int instance;
+};
+
+int osmo_gtlv_tag_inst_cmp(const struct osmo_gtlv_tag_inst *a, const struct
osmo_gtlv_tag_inst *b);
+
+int osmo_gtlv_tag_inst_to_str_buf(char *buf, size_t buflen, const struct
osmo_gtlv_tag_inst *ti,
+ const struct value_string *tag_names);
+char *osmo_gtlv_tag_inst_to_str_c(void *ctx, const struct osmo_gtlv_tag_inst *ti,
+ const struct value_string *tag_names);
+
+/*! TL configuration for osmo_gtlv_load*() and osmo_gtlv_put*(). Depending on these
implementations provided by the caller,
+ * osmo_gtlv can load any sizes of tag and length fields (that don't surpass the
value range of unsigned int and size_t,
+ * respectively), as well as TV (fixed-length) or TvLV (variable-sized length).
+ *
+ * See osmo_t8l8v_cfg and osmo_t16l16v_cfg, ready implementations for plain 8bit and
16bit TLV protocols.
+ *
+ * libosmo-pfcp serves as example for using this entire TLV API, uncluding de/encoding to
structs and generating parts
+ * of the TLV parsing code based on message definitions. It uses osmo_t16l16v_cfg.
+ */
+struct osmo_gtlv_cfg {
+ /*! The length in bytes of the shortest possible TL header (e.g. 4 for T16L16V, or 1 for
8bit tags where TV IEs
+ * without a length exist). A src_data_len passed to store_tl() below is guaranteed to
be >= this value. If at
+ * any point there is remaining message data smaller than this value, a parsing error is
returned.
+ */
+ size_t tl_min_size;
+
+ /*! Read one TL from the start of src_data.
+ * \param gtlv Return the T (tag) value read from src_data in gtlv->tag.
+ * Return the L (length) value read from src_data in gtlv->len.
+ * Return the I (instance) value read from src_data in gtlv->len; ignore
if there is no I.
+ * Return the position just after the TL in gtlv->*val. If there is V
data, point at the start of the
+ * V data in src_data. If there is no V data, point at the byte just after
the TL part in src_data.
+ * \param src_data Part of raw message being decoded.
+ * \param src_data_len Remaining message data length at src_data.
+ * \return 0 on success, negative on error.
+ */
+ int (*load_tl)(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t
src_data_len);
+
+ /*! Write a TL to dst_data, and return the size of the TL written.
+ * This is also invoked by osmo_gtlv_put_update_tl() to overwrite a previous TL header.
If the TL part's size
+ * can be different than the first time (e.g. due to a large L value in a TvLV
protocol), an implementation can
+ * use the 'gtlv' arg to figure out how to memmove the message data:
+ * When invoked by osmo_gtlv_put_tl(), dst_data == gtlv->dst->tail and
dst_data_avail == msgb_tailroom().
+ * When invoked by osmo_gtlv_put_update_tl(), dst_data < gtlv->dst->tail,
dst_data points at the start of the
+ * TL section written earlier by osmo_gtlv_put_tl() and dst_data_avail == the size of
the TL written earlier.
+ *
+ * \param dst_data Write TL data to the start of this buffer.
+ * \param dst_data_avail Remaining available space in dst_data.
+ * \param tag The T value to store in dst_data.
+ * \param instance The I value to store in dst_data (if this tag is a TLIV); ignore
when not a TLIV.
+ * \param len The L value to store in dst_data.
+ * \param gtlv Backpointer to the osmo_gtlv_put struct, including gtlv->dst, the
underlying msgb.
+ * \return the size of the TL part in bytes on success, -EINVAL if tag is invalid,
-EMSGSIZE if len is too large
+ * or dst_data_avail is too small for the TL.
+ */
+ int (*store_tl)(uint8_t *dst_data, size_t dst_data_avail, const struct
osmo_gtlv_tag_inst *ti, size_t len,
+ struct osmo_gtlv_put *gtlv);
+};
+
+/*! Configuration that allows parsing an 8bit tag and 8bit length TLV. */
+extern const struct osmo_gtlv_cfg osmo_t8l8v_cfg;
+
+/*! Configuration that allows parsing a 16bit tag and 16bit length TLV (see for example
PFCP). */
+extern const struct osmo_gtlv_cfg osmo_t16l16v_cfg;
+
+/*! State for loading a TLV structure from raw data. */
+struct osmo_gtlv_load {
+ /*! Caller-defined context pointer available for use by load_tl() and store_tl()
implementations. */
+ void *priv;
+
+ /*! Definition of tag and length sizes (by function pointers). */
+ const struct osmo_gtlv_cfg *cfg;
+
+ /*! Overall message buffer being parsed. */
+ struct {
+ const uint8_t *data;
+ size_t len;
+ } src;
+
+ /*! Return value from last invocation of osmo_gtlv_load_next*(): tag value of parsed IE.
*/
+ struct osmo_gtlv_tag_inst ti;
+ /*! Return value from last invocation of osmo_gtlv_load_next*(): Start of the IE's
payload data (after tag and
+ * length). If the end of the src buffer is reached, val == NULL. If a TLV contained no
value part, len == 0,
+ * but this still points just after the TL. */
+ const uint8_t *val;
+ /*! Return value from last invocation of osmo_gtlv_load_next*(): Length of the IE's
payload data (without tag and
+ * length) */
+ size_t len;
+};
+
+/* Start or restart the gtlv from the first IE in the overall TLV data. */
+static inline void osmo_gtlv_load_start(struct osmo_gtlv_load *gtlv)
+{
+ gtlv->val = NULL;
+}
+
+int osmo_gtlv_load_next(struct osmo_gtlv_load *gtlv);
+int osmo_gtlv_load_peek_tag(const struct osmo_gtlv_load *gtlv, struct osmo_gtlv_tag_inst
*ti);
+int osmo_gtlv_load_next_by_tag(struct osmo_gtlv_load *gtlv, unsigned int tag);
+int osmo_gtlv_load_next_by_tag_inst(struct osmo_gtlv_load *gtlv, const struct
osmo_gtlv_tag_inst *ti);
+
+/* State for storing a TLV structure into a msgb. */
+struct osmo_gtlv_put {
+ /*! Caller-defined context pointer available for use by load_tl() and store_tl()
implementations. */
+ void *priv;
+
+ /* Definition of tag and length sizes (by function pointers). */
+ const struct osmo_gtlv_cfg *cfg;
+
+ /* msgb to append new TL to */
+ struct msgb *dst;
+ /* What was the last TL written and where are its TL and V */
+ struct osmo_gtlv_tag_inst last_ti;
+ uint8_t *last_tl;
+ uint8_t *last_val;
+};
+
+int osmo_gtlv_put_tl(struct osmo_gtlv_put *gtlv, unsigned int tag, size_t len);
+int osmo_gtlv_put_tli(struct osmo_gtlv_put *gtlv, const struct osmo_gtlv_tag_inst *ti,
size_t len);
+int osmo_gtlv_put_update_tl(struct osmo_gtlv_put *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..132239f
--- /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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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, 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/include/osmocom/gtlv/gtlv_gen.h b/include/osmocom/gtlv/gtlv_gen.h
new file mode 100644
index 0000000..71cec92
--- /dev/null
+++ b/include/osmocom/gtlv/gtlv_gen.h
@@ -0,0 +1,176 @@
+/* Write h and c source files for TLV protocol definitions, based on very sparse TLV
definitions.
+ * For a usage example see tests/libosmo-gtlv/test_gtlv_gen/. */
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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>
+
+struct osmo_gtlv_gen_ie;
+
+/* O means optional, M means mandatory.
+ * If all of the IE struct, tag name and functions can be derived from the name, just
pass osmo_gtlv_gen_ie_auto as
+ * TLV_GEN_IE. */
+#define OSMO_GTLV_GEN_O(TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .optional = true, .ie =
&(TLV_GEN_IE) }
+#define OSMO_GTLV_GEN_M(TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .ie = &(TLV_GEN_IE) }
+#define OSMO_GTLV_GEN_O_MULTI(MAX, TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .multi = MAX, .ie
= &(TLV_GEN_IE) }
+#define OSMO_GTLV_GEN_M_MULTI(MAX, MAND_COUNT, TLV_GEN_IE, MEMB_NAME) \
+ { MEMB_NAME, .multi = MAX, .multi_mandatory = MAND_COUNT, .ie = &(TLV_GEN_IE) }
+#define OSMO_GTLV_GEN_O_INST(INSTANCE, TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .optional =
true, .instance = INSTANCE, .ie = &TLV_GEN_IE }
+#define OSMO_GTLV_GEN_M_INST(INSTANCE, TLV_GEN_IE, MEMB_NAME) { MEMB_NAME, .instance =
INSTANCE, .ie = &(TLV_GEN_IE) }
+
+#define OSMO_GTLV_GEN_NO_INSTANCE INT_MAX
+
+/*! osmo_gtlv_gen_ie with all members == NULL, so that all are derived from the member
name. */
+extern const struct osmo_gtlv_gen_ie osmo_gtlv_gen_ie_auto;
+
+/*! Modifier for Mandatory/Optional/Multiple around an osmo_gtlv_gen_ie. */
+struct osmo_gtlv_gen_ie_o {
+ /*! The C name of the member in a decoded struct, to be of the type defined by .ie.
+ * All parts of .ie, if NULL, are derived from this name.
+ *
+ * For example, simply this
+ *
+ * struct osmo_gtlv_gen_ie_o foo[] = {
+ * OSMO_GTLV_GEN_O("bar", NULL),
+ * };
+ *
+ * Generates
+ *
+ * struct myproto_msg_foo {
+ * struct myproto_ie_bar bar;
+ * }
+ *
+ * and an osmo_gtlv_coding entry of
+ *
+ * { MYPROTO_IEI_BAR,
+ * .memb_ofs = offsetof(struct myproto_msg_foo, bar),
+ * .dec_func = myproto_dec_bar,
+ * .enc_func = myproto_enc_bar,
+ * .enc_to_str_func = myproto_enc_to_str_bar,
+ * }
+ *
+ * See also osmo_gtlv_gen_cfg.add_enc_to_str.
+ */
+ const char *name;
+
+ /*! Whether to add a bool foo_present, and to skip encoding/decoding if false.
+ * Only useful for non-multi IEs (compare OSMO_GTLV_GEN_O_MULTI() vs
OSMO_GTLV_GEN_M_MULTI()). */
+ bool optional;
+
+ /*! If non-NULL, the member is an array: foo[123] with an unsigned int foo_count.
+ * Set to the maximum number of array elements; for foo[123] set .multi = 123. */
+ unsigned int multi;
+ /*! Number of mandatory occurences of the IE, only has an effect if .multi > 0. */
+ unsigned int multi_mandatory;
+
+ /* If any, the instance nr to match, in C that yields an unsigned int.
+ * e.g. "1" or "MYPROTO_FOO_INST_ONE". */
+ const char *instance;
+
+ /*! IE decoding / encoding instructions. If NULL, the entire IE definition is derived
from .name.
+ * 'MYPROTO_IEI_NAME', 'myproto_dec_name()',
'myproto_enc_name()', 'myproto_enc_to_str_name()'.
+ * Your myproto_ies_custom.h needs to define an enum value MYPROTO_IEI_NAME and*/
+ const struct osmo_gtlv_gen_ie *ie;
+};
+
+/*! Define decoding and encoding of a single IE, i.e. one full TLV. */
+struct osmo_gtlv_gen_ie {
+ /*! like "uint32_t" or "struct foo".
+ * If NULL, use "struct myproto_ie_<name>" instead, where <name>
comes from the osmo_gtlv_gen_ie_o.
+ * When there are nested IEs, the struct definition is auto-generated, deriving the
struct members from the
+ * nested_ies list.
+ * When there are no nested IEs, the type needs to be defined manually by a
myproto_ies_custom.h. */
+ const char *decoded_type;
+
+ /*! C name of this tag value, e.g. "foo" to use tag
"MYPROTO_IEI_FOO".
+ * If NULL, take "MYPROTO_IEI_"+upper(memb_name) instead, where memb_name
comes from the osmo_gtlv_gen_ie_o.
+ * decoded_type and/or dec_enc may be derived from this, if they are NULL. */
+ const char *tag_name;
+
+ /*! Name suffix of the dec/enc functions. "foo" -> myproto_dec_foo(),
myproto_enc_foo(),
+ * myproto_enc_to_str_foo().
+ * These functions need to be implemented manually in a myproto_ies_custom.c.
+ * When osmo_gtlv_gen_cfg.add_enc_to_str is false, the myproto_enc_to_str_foo() is not
required. */
+ const char *dec_enc;
+
+ /*! List of inner IEs terminated by {}. If non-NULL, this is a "Grouped IE"
with an inner TLV structure inside
+ * this IE's V part. */
+ const struct osmo_gtlv_gen_ie_o *nested_ies;
+
+ /*! To place a spec comment in the generated code. */
+ const char *spec_ref;
+};
+
+/*! General TLV decoding and encoding definitions applying to all IEs (and nested IEs).
*/
+struct osmo_gtlv_gen_cfg {
+ /*! Name of the protocol for use in C type or function names, like "myproto".
*/
+ const char *proto_name;
+
+ /*! When placing comments to spec references, prefix with this. For example, "3GPP
TS 12.345 ". */
+ const char *spec_ref_prefix;
+
+ /*! The type to pass a message discriminator as, like 'enum
myproto_message_types' */
+ const char *message_type_enum;
+ /*! To reference a message type discriminator like MYPROTO_MSGT_FOO, this would be
"MYPROTO_MSGT_". */
+ const char *message_type_prefix;
+
+ /*! Type to use to represent tag IEI in decoded form.
+ * For example "enum foo_msg_iei". */
+ const char *tag_enum;
+ /*! The tag IEI enum value is uppercase(tag_prefix + (iedef->tag_name or
iedef->name)).
+ * For example, with tag_prefix = "OSMO_FOO_IEI_", we would generate code
like
+ * enum osmo_foo_iei tag = OSMO_FOO_IEI_BAR; */
+ const char *tag_prefix;
+
+ /*! When an osmo_gtlv_gen_ie provides no decoded_type string, it is derived from .name
and this prefix is
+ * added. For example, with decoded_type_prefix = "struct foo_ie_", the
decoded_type defaults to
+ * struct foo_ie_bar for an IE definition with name = "bar". */
+ const char *decoded_type_prefix;
+
+ /*! To include user defined headers, set to something like "#include
<osmocom/foo/foo_tlv_devs.h". This is put at
+ * the head of the generated .h file. */
+ const char *h_header;
+
+ /*! To include user defined headers, set to something like "#include
<osmocom/foo/foo_msg.h". This is put at
+ * the head of the generated .c file. */
+ const char *c_header;
+
+ /*! Array of message IE definitions, indexed by message type. */
+ const struct osmo_gtlv_gen_msg *msg_defs;
+
+ /*! Whether to add to_str functions. When true, every automatically derived IE (that has
no nested IEs) needs to
+ * have a myproto_enc_to_str_foo() defined by a myproto_ies_custom.c. When false,
osmo_gtlvs_encode_to_str_buf()
+ * will print '?' instead of the IE contents. */
+ bool add_enc_to_str;
+};
+
+/*! For generating the outer union that composes a protocol's PDU variants, an entry
of the list of message names and
+ * IEs in each message. */
+struct osmo_gtlv_gen_msg {
+ const char *name;
+ const struct osmo_gtlv_gen_ie_o *ies;
+};
+
+int osmo_gtlv_gen_main(const struct osmo_gtlv_gen_cfg *cfg, int argc, const char
**argv);
diff --git a/libosmo-gtlv.pc.in b/libosmo-gtlv.pc.in
new file mode 100644
index 0000000..69de862
--- /dev/null
+++ b/libosmo-gtlv.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom Generic TLV Library
+Description: C Utility Library
+Version: @VERSION@
+Libs: -L${libdir} -losmo-gtlv
+Cflags: -I${includedir}/
diff --git a/src/libosmo-gtlv/Makefile.am b/src/libosmo-gtlv/Makefile.am
new file mode 100644
index 0000000..f181b3a
--- /dev/null
+++ b/src/libosmo-gtlv/Makefile.am
@@ -0,0 +1,29 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ $(TALLOC_CFLAGS) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+lib_LTLIBRARIES = \
+ libosmo-gtlv.la \
+ $(NULL)
+
+libosmo_gtlv_la_SOURCES = \
+ gtlv.c \
+ gtlv_dec_enc.c \
+ gtlv_gen.c \
+ $(NULL)
+
+libosmo_gtlv_la_LIBADD = \
+ $(top_builddir)/src/libosmocore.la \
+ $(NULL)
diff --git a/src/libosmo-gtlv/gtlv.c b/src/libosmo-gtlv/gtlv.c
new file mode 100644
index 0000000..9bdbe75
--- /dev/null
+++ b/src/libosmo-gtlv/gtlv.c
@@ -0,0 +1,342 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gtlv/gtlv.h>
+
+int osmo_gtlv_tag_inst_cmp(const struct osmo_gtlv_tag_inst *a, const struct
osmo_gtlv_tag_inst *b)
+{
+ int cmp;
+ if (a == b)
+ return 0;
+ if (!a)
+ return -1;
+ if (!b)
+ return 1;
+ cmp = OSMO_CMP(a->tag, b->tag);
+ if (cmp)
+ return cmp;
+ cmp = OSMO_CMP(a->instance_present ? 1 : 0, b->instance_present ? 1 : 0);
+ if (cmp)
+ return cmp;
+ if (a->instance_present)
+ return OSMO_CMP(a->instance, b->instance);
+ return 0;
+}
+
+int osmo_gtlv_tag_inst_to_str_buf(char *buf, size_t buflen, const struct
osmo_gtlv_tag_inst *ti,
+ const struct value_string *tag_names)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ if (!tag_names)
+ OSMO_STRBUF_PRINTF(sb, "%u", ti->tag);
+ else
+ OSMO_STRBUF_PRINTF(sb, "%s", get_value_string(tag_names, ti->tag));
+ if (ti->instance_present)
+ OSMO_STRBUF_PRINTF(sb, "[%u]", ti->instance);
+ return sb.chars_needed;
+}
+
+char *osmo_gtlv_tag_inst_to_str_c(void *ctx, const struct osmo_gtlv_tag_inst *ti,
+ const struct value_string *tag_names)
+{
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_gtlv_tag_inst_to_str_buf, ti,
tag_names)
+}
+
+static int next_tl_valid(const struct osmo_gtlv_load *gtlv, const uint8_t **ie_start_p,
size_t *buflen_left_p)
+{
+ const uint8_t *ie_start;
+ size_t buflen_left;
+
+ /* Start of next IE, or first IE for first invocation. */
+ if (!gtlv->val)
+ ie_start = gtlv->src.data;
+ else
+ ie_start = gtlv->val + gtlv->len;
+
+ /* Sanity */
+ if (ie_start < gtlv->src.data || ie_start > gtlv->src.data +
gtlv->src.len)
+ return -ENOSPC;
+
+ buflen_left = gtlv->src.len - (ie_start - gtlv->src.data);
+
+ /* Too short for parsing an IE? Check also against integer overflow. */
+ if (buflen_left && ((buflen_left < gtlv->cfg->tl_min_size) ||
(buflen_left > gtlv->src.len)))
+ return -EBADMSG;
+
+ *ie_start_p = ie_start;
+ *buflen_left_p = buflen_left;
+ return 0;
+}
+
+/* Return a TLV IE from a message buffer.
+ *
+ * Return the first or next TLV data found in the data buffer, based on the state of the
gtlv parameter.
+ * When gtlv->val is NULL, return the first IE in the data buffer.
+ * Otherwise assume that gtlv points at a valid IE in the data structure, and return the
subsequent IE.
+ *
+ * Usage example:
+ *
+ * struct osmo_gtlv gtlv = {
+ * .cfg = osmo_t16l16v_cfg,
+ * .src = { .data = msgb_l3(msg), .len = msgb_l3len(msg) },
+ * };
+ * for (;;) {
+ * if (osmo_gtlv_next(>lv)) {
+ * printf("Error\n");
+ * break;
+ * }
+ * if (!gtlv.val) {
+ * printf("End\n");
+ * break;
+ * }
+ * printf("Tag %u: %zu octets: %s\n", gtlv.tag, gtlv.len,
osmo_hexdump(gtlv.val, gtlv.len));
+ * }
+ *
+ * \param[inout] gtlv Buffer to return the IE data, and state for TLV parsing position.
gtlv->msg should indicate the
+ * overall message buffer. The other gtlv members should be zero
initialized before the first call, and
+ * remain unchanged between invocations of this function.
+ * \returns 0 on success, negative on TLV parsing error. The IE data is returned in
gtlv->tag, gtlv->len and gtlv->val;
+ * gtlv->val == NULL if no more IEs remain in the buffer.
+ */
+int osmo_gtlv_load_next(struct osmo_gtlv_load *gtlv)
+{
+ const uint8_t *ie_start;
+ const uint8_t *ie_end;
+ size_t buflen_left;
+ int rc;
+
+ rc = next_tl_valid(gtlv, &ie_start, &buflen_left);
+ if (rc)
+ return rc;
+
+ /* No more IEs? */
+ if (!buflen_left) {
+ gtlv->val = NULL;
+ return 0;
+ }
+
+ /* Locate next IE */
+ OSMO_ASSERT(gtlv->cfg->load_tl);
+ gtlv->ti = (struct osmo_gtlv_tag_inst){};
+ rc = gtlv->cfg->load_tl(gtlv, ie_start, buflen_left);
+ if (rc)
+ return rc;
+
+ /* Sanity */
+ ie_end = gtlv->val + gtlv->len;
+ if (ie_end < gtlv->src.data || ie_end > gtlv->src.data + gtlv->src.len)
+ return -EBADMSG;
+
+ return 0;
+}
+
+/* Return the tag of the IE that osmo_gtlv_next() would yield, do not change the gtlv
state.
+ *
+ * \param[in] gtlv state for TLV parsing position; is not modified.
+ * \param[out] tag the tag number on success, if NULL don't return the tag.
+ * \param[out] instance the instance number or OSMO_GTLV_NO_INSTANCE if there is no
instance value,
+ * if NULL don't return the instance value.
+ * \returns 0 on success, negative on TLV parsing error, -ENOENT when no more tags
follow.
+ */
+int osmo_gtlv_load_peek_tag(const struct osmo_gtlv_load *gtlv, struct osmo_gtlv_tag_inst
*ti)
+{
+ const uint8_t *ie_start;
+ size_t buflen_left;
+ int rc;
+ /* Guard against modification by load_tl(). */
+ struct osmo_gtlv_load mtlv = *gtlv;
+ mtlv.ti = (struct osmo_gtlv_tag_inst){};
+
+ rc = next_tl_valid(&mtlv, &ie_start, &buflen_left);
+ if (rc)
+ return rc;
+
+ if (!buflen_left)
+ return -ENOENT;
+
+ /* Return next IE tag*/
+ OSMO_ASSERT(mtlv.cfg->load_tl);
+ rc = gtlv->cfg->load_tl(&mtlv, ie_start, buflen_left);
+ if (rc)
+ return -EBADMSG;
+ if (ti)
+ *ti = mtlv.ti;
+ return 0;
+}
+
+/* Same as osmo_gtlv_load_next(), but skip any IEs until the given tag is reached. Change
the gtlv state only when success
+ * is returned.
+ * \param[out] gtlv Return the next IE's TLV info.
+ * \param[in] tag Tag value to match.
+ * \param[in] instance Instance value to match; For IEs that have no instance value (no
TLIV), pass
+ * OSMO_GTLV_NO_INSTANCE.
+ * \return 0 when the tag is found. Return -ENOENT when no such tag follows and keep the
gtlv unchanged. */
+int osmo_gtlv_load_next_by_tag(struct osmo_gtlv_load *gtlv, unsigned int tag)
+{
+ struct osmo_gtlv_tag_inst ti = { .tag = tag };
+ return osmo_gtlv_load_next_by_tag_inst(gtlv, &ti);
+}
+
+int osmo_gtlv_load_next_by_tag_inst(struct osmo_gtlv_load *gtlv, const struct
osmo_gtlv_tag_inst *ti)
+{
+ struct osmo_gtlv_load work = *gtlv;
+ for (;;) {
+ int rc = osmo_gtlv_load_next(&work);
+ if (rc)
+ return rc;
+ if (!work.val)
+ return -ENOENT;
+ if (!osmo_gtlv_tag_inst_cmp(&work.ti, ti)) {
+ *gtlv = work;
+ return 0;
+ }
+ }
+}
+
+/* Put tag header and length at the end of the msgb, according to
gtlv->cfg->store_tl().
+ * If the length is not known yet, it can be passed as 0 at first, and
osmo_gtlv_put_update_tl() can determine the
+ * resulting length after the value part was put into the msgb.
+ *
+ * Usage example:
+ *
+ * struct msgb *msg = msgb_alloc(1024, "foo"),
+ * struct osmo_gtlv_put gtlv = {
+ * .cfg = osmo_t16l16v_cfg,
+ * .dst = msg,
+ * }
+ *
+ * osmo_gtlv_put_tl(gtlv, 23, 0); // tag 23, length 0 = not known yet
+ *
+ * msgb_put(msg, 42);
+ * ...
+ * msgb_put(msg, 42);
+ * ...
+ * msgb_put(msg, 42);
+ *
+ * osmo_gtlv_put_update_tl(gtlv);
+ *
+ * Return 0 on success, -EINVAL if the tag value is invalid, -EMSGSIZE if len is too
large.
+ */
+int osmo_gtlv_put_tl(struct osmo_gtlv_put *gtlv, unsigned int tag, size_t len)
+{
+ struct osmo_gtlv_tag_inst ti = { .tag = tag };
+ return osmo_gtlv_put_tli(gtlv, &ti, len);
+}
+
+/* Put tag header, instance value and length at the end of the msgb, according to
gtlv->cfg->store_tl().
+ * This is the same as osmo_gtlv_put_tl(), only osmo_gtlv_put_tl() passes instance = 0.
+ */
+int osmo_gtlv_put_tli(struct osmo_gtlv_put *gtlv, const struct osmo_gtlv_tag_inst *ti,
size_t len)
+{
+ int rc;
+ uint8_t *last_tl;
+ OSMO_ASSERT(gtlv->cfg->store_tl);
+ last_tl = gtlv->dst->tail;
+ rc = gtlv->cfg->store_tl(gtlv->dst->tail, msgb_tailroom(gtlv->dst), ti,
len, gtlv);
+ if (rc < 0)
+ return rc;
+ if (rc > 0)
+ msgb_put(gtlv->dst, rc);
+ gtlv->last_ti = *ti;
+ gtlv->last_tl = last_tl;
+ gtlv->last_val = gtlv->dst->tail;
+ return 0;
+}
+
+/* Update the length of the last put IE header (last call to osmo_gtlv_put_tl()) to match
with the current
+ * gtlv->dst->tail.
+ * Return 0 on success, -EMSGSIZE if the amount of data written since osmo_gtlv_put_tl()
is too large.
+ */
+int osmo_gtlv_put_update_tl(struct osmo_gtlv_put *gtlv)
+{
+ size_t len = gtlv->dst->tail - gtlv->last_val;
+ int rc = gtlv->cfg->store_tl(gtlv->last_tl, gtlv->last_val -
gtlv->last_tl, >lv->last_ti, len, gtlv);
+ if (rc < 0)
+ return rc;
+ /* In case the TL has changed in size, hopefully the implementation has moved the msgb
data. Make sure last_val
+ * points at the right place now. */
+ gtlv->last_val = gtlv->last_tl + rc;
+ return 0;
+}
+
+static int t8l8v_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t
src_data_len)
+{
+ /* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 2.
*/
+ gtlv->ti.tag = src_data[0];
+ gtlv->len = src_data[1];
+ gtlv->val = src_data + 2;
+ return 0;
+}
+
+static int t8l8v_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct
osmo_gtlv_tag_inst *ti, size_t len,
+ struct osmo_gtlv_put *gtlv)
+{
+ if (ti->tag > UINT8_MAX)
+ return -EINVAL;
+ if (len > UINT8_MAX)
+ return -EMSGSIZE;
+ if (dst_data_avail < 2)
+ return -ENOSPC;
+ dst_data[0] = ti->tag;
+ dst_data[1] = len;
+ return 2;
+}
+
+const struct osmo_gtlv_cfg osmo_t8l8v_cfg = {
+ .tl_min_size = 2,
+ .load_tl = t8l8v_load_tl,
+ .store_tl = t8l8v_store_tl,
+};
+
+static int t16l16v_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t
src_data_len)
+{
+ /* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 4.
*/
+ gtlv->ti.tag = osmo_load16be(src_data);
+ gtlv->len = osmo_load16be(src_data + 2);
+ gtlv->val = src_data + 4;
+ return 0;
+}
+
+static int t16l16v_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct
osmo_gtlv_tag_inst *ti, size_t len,
+ struct osmo_gtlv_put *gtlv)
+{
+ if (ti->tag > UINT16_MAX)
+ return -EINVAL;
+ if (len > UINT16_MAX)
+ return -EMSGSIZE;
+ if (dst_data_avail < 4)
+ return -ENOSPC;
+ osmo_store16be(ti->tag, dst_data);
+ osmo_store16be(len, dst_data + 2);
+ return 4;
+}
+
+const struct osmo_gtlv_cfg osmo_t16l16v_cfg = {
+ .tl_min_size = 4,
+ .load_tl = t16l16v_load_tl,
+ .store_tl = t16l16v_store_tl,
+};
diff --git a/src/libosmo-gtlv/gtlv_dec_enc.c b/src/libosmo-gtlv/gtlv_dec_enc.c
new file mode 100644
index 0000000..2e5509a
--- /dev/null
+++ b/src/libosmo-gtlv/gtlv_dec_enc.c
@@ -0,0 +1,530 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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, TAG_INST, FMT, ARGS...) \
+ do {\
+ if (err_cb) { \
+ if ((TAG_INST).instance_present) \
+ err_cb(err_cb_data, (void *)decoded_struct, __FILE__, __LINE__, \
+ "tag 0x%x = %s instance %u: " FMT " (%d: %s)\n", \
+ (TAG_INST).tag, get_value_string(iei_strs, (TAG_INST).tag), \
+ (TAG_INST).instance, ##ARGS, \
+ RC, strerror((RC) > 0 ? (RC) : -(RC))); \
+ else \
+ err_cb(err_cb_data, (void *)decoded_struct, __FILE__, __LINE__, \
+ "tag 0x%x = %s: " FMT " (%d: %s)\n", \
+ (TAG_INST).tag, get_value_string(iei_strs, (TAG_INST).tag), ##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->ti, \
+ "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->ti, "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) && osmo_gtlv_tag_inst_cmp(&iec->ti,
>lv->ti);
+ 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->ti, "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->ti, "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->ti, "IE definition lacks a dec_func()");
+ rc = iec->dec_func(decoded_struct, MEMB(obj, memb_ofs), gtlv);
+ if (rc)
+ RETURN_ERROR(rc, gtlv->ti, "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->ti, "%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->ti, "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;
+ struct osmo_gtlv_tag_inst peek_ti;
+
+ rc = osmo_gtlv_load_next_by_tag_inst(gtlv, &ie_coding->ti);
+ switch (rc) {
+ case 0:
+ break;
+ case -ENOENT:
+ if (!presence_flag && (!multi_count || *multi_count <
ie_coding->count_mandatory))
+ RETURN_ERROR(rc, ie_coding->ti, "Missing mandatory IE");
+ if (presence_flag)
+ *presence_flag = false;
+ continue;
+ default:
+ RETURN_ERROR(rc, ie_coding->ti, "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->ti, "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->ti,
+ "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->ti, "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->ti, "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, &peek_ti)
+ || osmo_gtlv_tag_inst_cmp(&peek_ti, >lv->ti)) {
+ /* 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->ti,
+ "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_tli(gtlv, &ie_coding->ti, 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->ti,
+ "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->ti,
+ "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->ti, "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->ti.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/src/libosmo-gtlv/gtlv_gen.c b/src/libosmo-gtlv/gtlv_gen.c
new file mode 100644
index 0000000..fd3fbd9
--- /dev/null
+++ b/src/libosmo-gtlv/gtlv_gen.c
@@ -0,0 +1,420 @@
+/* Write h and c source files for TLV protocol definitions, based on very sparse TLV
definitions.
+ * For a usage example see tests/libosmo-gtlv/test_gtlv_gen/. */
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <string.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gtlv/gtlv_gen.h>
+
+static const struct osmo_gtlv_gen_cfg *g_cfg = NULL;
+
+const struct osmo_gtlv_gen_ie osmo_gtlv_gen_ie_auto = {};
+
+/* Helps avoid redundant definitions of the same type. */
+struct seen_entry {
+ struct llist_head entry;
+ char str[256];
+ const void *from_def;
+};
+static LLIST_HEAD(seen_list);
+
+static bool seen(const char *str, const void *from_def)
+{
+ struct seen_entry *s;
+ llist_for_each_entry(s, &seen_list, entry) {
+ if (!strcmp(s->str, str)) {
+ if (from_def != s->from_def) {
+ fprintf(stderr, "ERROR: %s: multiple definitions use the same name:
'%s'\n",
+ g_cfg->proto_name, str);
+ exit(1);
+ }
+ return true;
+ }
+ }
+ s = talloc_zero(NULL, struct seen_entry);
+ OSMO_STRLCPY_ARRAY(s->str, str);
+ s->from_def = from_def;
+ llist_add(&s->entry, &seen_list);
+ return false;
+}
+static void clear_seen()
+{
+ struct seen_entry *s;
+ while ((s = llist_first_entry_or_null(&seen_list, struct seen_entry, entry))) {
+ llist_del(&s->entry);
+ talloc_free(s);
+ }
+}
+
+/* Return "struct foo_ie_bar" from g_cfg->decoded_type_prefix and ie. */
+static inline const char *decoded_type(const struct osmo_gtlv_gen_ie_o *ie_o)
+{
+ static char b[255];
+ const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
+ const char *tag_name;
+ if (ie && ie->decoded_type)
+ return ie->decoded_type;
+ /* "struct foo_ie_" + "bar" = struct foo_ie_bar*/
+ tag_name = ie ? ie->tag_name : NULL;
+ snprintf(b, sizeof(b), "%s%s", g_cfg->decoded_type_prefix, tag_name ? :
ie_o->name);
+ return b;
+}
+
+/* --- .h file --- */
+
+/* Write a listing of struct members like
+ * bool foo_present;
+ * int foo;
+ * struct myproto_ie_bar bar;
+ * struct abc abc[10];
+ * int abc_count;
+ */
+static void write_ie_members(const struct osmo_gtlv_gen_ie_o ies[])
+{
+ const struct osmo_gtlv_gen_ie_o *ie_o;
+ for (ie_o = ies; ie_o->ie; ie_o++) {
+ if (ie_o->optional)
+ printf("\tbool %s_present;\n", ie_o->name);
+ printf("\t%s %s", decoded_type(ie_o), ie_o->name);
+ if (ie_o->multi) {
+ printf("[%u];\n", ie_o->multi);
+ printf("\tunsigned int %s_count", ie_o->name);
+ }
+ printf(";\n");
+ }
+}
+
+/* Traverse nesting levels in the message definitions and generate the structs for all as
needed. */
+static void write_ie_auto_structs(const struct osmo_gtlv_gen_ie_o ies[])
+{
+ const struct osmo_gtlv_gen_ie_o *ie_o;
+ if (!ies)
+ return;
+ for (ie_o = ies; ie_o->ie; ie_o++) {
+ const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
+ if (!ie || !ie->nested_ies)
+ continue;
+ /* Recurse to write inner layers first, so that they can be referenced in outer layers.
*/
+ write_ie_auto_structs(ie->nested_ies);
+
+ /* Various IE definitions can use the same underlying type. Only generate each type
once. */
+ if (seen(decoded_type(ie_o), NULL))
+ continue;
+
+ /* Print:
+ *
+ * \* spec ref *\
+ * struct myproto_ie_goo {
+ * bool foo_present;
+ * int foo;
+ * struct myproto_ie_bar bar;
+ * struct abc abc[10];
+ * int abc_count;
+ * };
+ */
+ printf("\n");
+ if (ie->spec_ref)
+ printf("/* %s%s */\n", g_cfg->spec_ref_prefix, ie->spec_ref);
+ printf("%s {\n", decoded_type(ie_o));
+ write_ie_members(ie->nested_ies);
+ printf("};\n");
+ }
+}
+
+/* Write all auto-generated structs, starting with the outer message definitions and
nesting into all contained IE
+ * definitions. */
+static void write_auto_structs()
+{
+ const struct osmo_gtlv_gen_msg *gen_msg;
+ clear_seen();
+ for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
+ write_ie_auto_structs(gen_msg->ies);
+ }
+}
+
+/* Write the struct definitions for each message, i.e. for each entry in the outer
PDU's message union, as well as the
+ * union itself.
+ *
+ * struct myproto_msg_foo {
+ * ...
+ * }:
+ * struct myproto_msg_goo {
+ * ...
+ * };
+ * union myproto_ies {
+ * myproto_msg_foo foo;
+ * myproto_msg_goo goo;
+ * };
+ */
+static void write_msg_union()
+{
+ const struct osmo_gtlv_gen_msg *gen_msg;
+ for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
+ /* "struct foo_msg" + "_%s" { *
+ * struct foo_msg_goo_request { ... }; */
+ printf("\nstruct %s_msg_%s {\n",
+ g_cfg->proto_name,
+ gen_msg->name);
+ write_ie_members(gen_msg->ies);
+ printf("};\n");
+ }
+
+ printf("\nunion %s_ies {\n", g_cfg->proto_name);
+ for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
+ printf("\tstruct %s_msg_%s %s;\n", g_cfg->proto_name,
+ gen_msg->name, gen_msg->name);
+ }
+ printf("};\n");
+}
+
+/* Write the C header, myproto_ies_auto.h */
+static void write_h()
+{
+ printf("/* THIS FILE IS GENERATED FROM %s */\n", __FILE__);
+ printf("#include <stdint.h>\n");
+ printf("#include <osmocom/gtlv/gtlv_dec_enc.h>\n");
+ if (g_cfg->h_header)
+ printf("\n%s\n", g_cfg->h_header);
+ write_auto_structs();
+ write_msg_union();
+ printf("\nconst struct osmo_gtlv_coding *%s_get_msg_coding(%s
message_type);\n",
+ g_cfg->proto_name, g_cfg->message_type_enum ? : "int");
+ printf("\n"
+ "int %s_ies_decode(union %s_ies *dst, struct osmo_gtlv_load *gtlv, bool
tlv_ordered,\n"
+ " %s message_type, osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct
value_string *iei_strs);\n",
+ g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? :
"int");
+ printf("\n"
+ "int %s_ies_encode(struct osmo_gtlv_put *gtlv, const union %s_ies *src,\n"
+ " %s message_type, osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct
value_string *iei_strs);\n",
+ g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? :
"int");
+ printf("\n"
+ "int %s_ies_encode_to_str(char *buf, size_t buflen, const union %s_ies
*src,\n"
+ " %s message_type, const struct value_string *iei_strs);\n",
+ g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? :
"int");
+}
+
+/* --- .c file --- */
+
+/* Write a listing of:
+ * extern int myproto_dec_foo(...);
+ * extern int myproto_enc_foo(...);
+ */
+static void write_extern_dec_enc(const struct osmo_gtlv_gen_ie_o *ies)
+{
+ const struct osmo_gtlv_gen_ie_o *ie_o;
+ for (ie_o = ies; ie_o->ie; ie_o++) {
+ const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
+ const char *dec_enc = ie_o->name;
+ if (ie)
+ dec_enc = ie->dec_enc ? : (ie->tag_name ? : ie_o->name);
+ if (ie && ie->nested_ies) {
+ write_extern_dec_enc(ie->nested_ies);
+ continue;
+ }
+ if (seen(dec_enc, NULL))
+ continue;
+ printf("extern int %s_dec_%s(void *decoded_struct, void *decode_to, const struct
osmo_gtlv_load *gtlv);\n",
+ g_cfg->proto_name, dec_enc);
+ printf("extern int %s_enc_%s(struct osmo_gtlv_put *gtlv, const void
*decoded_struct, const void *encode_from);\n",
+ g_cfg->proto_name, dec_enc);
+ if (g_cfg->add_enc_to_str)
+ printf("extern int %s_enc_to_str_%s(char *buf, size_t buflen, const void
*encode_from);\n",
+ g_cfg->proto_name, dec_enc);
+ }
+}
+
+/* For a nested IE, write the struct osmo_gtlv_coding array of the inner IEs.
+ * { { MYPROTO_IEI_BAR },
+ * .memb_ofs = offsetof(struct myproto_foo, bar),
+ * .dec_func = myproto_dec_bar,
+ * .enc_func = myproto_enc_bar,
+ * },
+ */
+static void write_ies_array(const char *indent, const struct osmo_gtlv_gen_ie_o *ies,
const char *obj_type, const char *substruct)
+{
+#define printi(FMT, ARGS...) printf("%s" FMT, indent, ##ARGS)
+
+ const struct osmo_gtlv_gen_ie_o *ie_o;
+ for (ie_o = ies; ie_o->ie; ie_o++) {
+ const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
+ const char *tag_name = (ie && ie->tag_name) ? ie->tag_name :
ie_o->name;
+ printi("{ { %s%s", g_cfg->tag_prefix, osmo_str_toupper(tag_name));
+ if (ie_o->instance)
+ printf(", true, %s", ie_o->instance);
+ printf(" },\n");
+ printi(" .memb_ofs = offsetof(%s, %s%s),\n", obj_type, substruct,
ie_o->name);
+ if (ie->nested_ies) {
+ printi(" .nested_ies = ies_in_%s,\n", tag_name);
+ } else {
+ const char *dec_enc = ie->dec_enc ? : (ie->tag_name ? : ie_o->name);
+ printi(" .dec_func = %s_dec_%s,\n", g_cfg->proto_name, dec_enc);
+ printi(" .enc_func = %s_enc_%s,\n", g_cfg->proto_name, dec_enc);
+ if (g_cfg->add_enc_to_str)
+ printi(" .enc_to_str_func = %s_enc_to_str_%s,\n", g_cfg->proto_name,
dec_enc);
+ }
+ if (ie_o->multi) {
+ printi(" .memb_array_pitch = OSMO_MEMB_ARRAY_PITCH(%s, %s%s),\n",
+ obj_type, substruct, ie_o->name);
+ printi(" .has_count = true, .count_max = %u,\n", ie_o->multi);
+ printi(" .count_mandatory = %u,\n", ie_o->multi_mandatory);
+ printi(" .count_ofs = offsetof(%s, %s%s_count),\n", obj_type, substruct,
ie_o->name);
+ }
+ if (ie_o->optional) {
+ printi(" .has_presence_flag = true,\n");
+ printi(" .presence_flag_ofs = offsetof(%s, %s%s_present),\n", obj_type,
substruct, ie_o->name);
+ }
+ printi("},\n");
+ }
+}
+
+/* For a nested IE, write the struct osmo_gtlv_coding array of the inner IEs.
+ * static const struct osmo_gtlv_coding ies_in_foo[] = {
+ * { {MYPROTO_IEI_BAR},
+ * .memb_ofs = offsetof(struct myproto_foo, bar),
+ * .dec_func = myproto_dec_bar,
+ * .enc_func = myproto_enc_bar,
+ * },
+ * ...
+ * };
+ */
+static void write_nested_ies_array(const struct osmo_gtlv_gen_ie_o *ies)
+{
+ const char *indent = "\t";
+ const struct osmo_gtlv_gen_ie_o *ie_o;
+ for (ie_o = ies; ie_o->ie; ie_o++) {
+ const struct osmo_gtlv_gen_ie *ie = ie_o->ie;
+ if (!ie || !ie->nested_ies)
+ continue;
+ write_nested_ies_array(ie->nested_ies);
+
+ const char *ies_in_name = ie->tag_name ? : ie_o->name;
+ if (seen(ies_in_name, ie))
+ continue;
+
+ printf("\nstatic const struct osmo_gtlv_coding ies_in_%s[] = {\n",
ies_in_name);
+ write_ies_array(indent, ie->nested_ies, decoded_type(ie_o), "");
+ printi("{}\n");
+ printf("};\n");
+ }
+}
+
+/* Write the bulk of the C code: on the basis of the list of messages
(g_cfg->msg_defs), write all dec/enc function
+ * declarations, all IEs arrays as well as the list of message types, first triggering to
write the C code for any inner
+ * layers. */
+static void write_c()
+{
+ const struct osmo_gtlv_gen_msg *gen_msg;
+
+ printf("/* THIS FILE IS GENERATED FROM %s */\n", __FILE__);
+ printf("#include <stddef.h>\n");
+ printf("#include <errno.h>\n");
+ printf("#include <osmocom/core/utils.h>\n");
+ printf("#include <osmocom/gtlv/gtlv.h>\n");
+ printf("#include <osmocom/gtlv/gtlv_dec_enc.h>\n");
+ printf("#include <osmocom/gtlv/gtlv_gen.h>\n");
+ if (g_cfg->c_header)
+ printf("\n%s\n", g_cfg->c_header);
+
+ printf("\n");
+ clear_seen();
+ for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
+ write_extern_dec_enc(gen_msg->ies);
+ }
+
+ clear_seen();
+ for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
+ write_nested_ies_array(gen_msg->ies);
+ }
+
+ for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
+ char *obj_type = talloc_asprintf(NULL, "union %s_ies",
g_cfg->proto_name);
+ char *substruct = talloc_asprintf(NULL, "%s.", gen_msg->name);
+ printf("\nstatic const struct osmo_gtlv_coding ies_in_msg_%s[] = {\n",
gen_msg->name);
+ write_ies_array("\t", gen_msg->ies, obj_type, substruct);
+ printf("\t{}\n};\n");
+ talloc_free(substruct);
+ talloc_free(obj_type);
+ }
+ printf("\nstatic const struct osmo_gtlv_coding *msg_defs[] = {\n");
+ for (gen_msg = g_cfg->msg_defs; gen_msg->name; gen_msg++) {
+ printf("\t[%s%s] = ies_in_msg_%s,\n", g_cfg->message_type_prefix,
osmo_str_toupper(gen_msg->name), gen_msg->name);
+ }
+ printf("};\n");
+
+ /* print this code snippet into the .c file, because only there can we do
ARRAY_SIZE(foo_msg_coding). */
+ printf("\n"
+ "const struct osmo_gtlv_coding *%s_get_msg_coding(%s message_type)\n"
+ "{\n"
+ " if (message_type >= ARRAY_SIZE(msg_defs))\n"
+ " return NULL;\n"
+ " return msg_defs[message_type];\n"
+ "}\n",
+ g_cfg->proto_name, g_cfg->message_type_enum ? : "int");
+
+ printf("\n"
+ "int %s_ies_decode(union %s_ies *dst, struct osmo_gtlv_load *gtlv, bool
tlv_ordered,\n"
+ " %s message_type,\n"
+ " osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string
*iei_strs)\n"
+ "{\n"
+ " return osmo_gtlvs_decode(dst, 0, gtlv, tlv_ordered,
%s_get_msg_coding(message_type), err_cb, err_cb_data, iei_strs);\n"
+ "}\n",
+ g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? :
"int", g_cfg->proto_name);
+ printf("\n"
+ "int %s_ies_encode(struct osmo_gtlv_put *gtlv, const union %s_ies *src,\n"
+ " %s message_type, osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct
value_string *iei_strs)\n"
+ "{\n"
+ " return osmo_gtlvs_encode(gtlv, src, 0, %s_get_msg_coding(message_type), err_cb,
err_cb_data, iei_strs);\n"
+ "}\n",
+ g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? :
"int", g_cfg->proto_name);
+ printf("\n"
+ "int %s_ies_encode_to_str(char *buf, size_t buflen, const union %s_ies
*src,\n"
+ " %s message_type, const struct value_string *iei_strs)\n"
+ "{\n"
+ " return osmo_gtlvs_encode_to_str_buf(buf, buflen, src, 0,
%s_get_msg_coding(message_type), iei_strs);\n"
+ "}\n",
+ g_cfg->proto_name, g_cfg->proto_name, g_cfg->message_type_enum ? :
"int", g_cfg->proto_name);
+}
+
+/* Call this from your main(). */
+int osmo_gtlv_gen_main(const struct osmo_gtlv_gen_cfg *cfg, int argc, const char **argv)
+{
+ if (argc < 2)
+ return 1;
+
+ g_cfg = cfg;
+
+ if (strcmp(argv[1], "h") == 0)
+ write_h();
+ else if (strcmp(argv[1], "c") == 0)
+ write_c();
+ else
+ return 1;
+
+ clear_seen();
+ return 0;
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f54ce18..b8452a2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,3 +1,7 @@
+SUBDIRS = \
+ libosmo-gtlv \
+ $(NULL)
+
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
AM_LDFLAGS = -no-install
diff --git a/tests/libosmo-gtlv/Makefile.am b/tests/libosmo-gtlv/Makefile.am
new file mode 100644
index 0000000..1a8e69b
--- /dev/null
+++ b/tests/libosmo-gtlv/Makefile.am
@@ -0,0 +1,53 @@
+SUBDIRS = \
+ test_gtlv_gen \
+ test_tliv \
+ $(NULL)
+
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gtlv_test \
+ gtlv_dec_enc_test \
+ $(NULL)
+
+EXTRA_DIST = \
+ gtlv_test.ok \
+ gtlv_dec_enc_test.ok \
+ $(NULL)
+
+gtlv_test_SOURCES = \
+ gtlv_test.c \
+ $(NULL)
+
+gtlv_test_LDADD = \
+ $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.la \
+ $(TALLOC_LIBS) \
+ $(PTHREAD_LIBS) \
+ $(NULL)
+
+gtlv_dec_enc_test_SOURCES = \
+ gtlv_dec_enc_test.c \
+ $(NULL)
+
+gtlv_dec_enc_test_LDADD = \
+ $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.la \
+ $(TALLOC_LIBS) \
+ $(PTHREAD_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
+ $(MAKE) -C test_gtlv_gen update_exp
+ $(MAKE) -C test_tliv update_exp
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..e02a2e5
--- /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(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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[] = {
+ {
+ .ti = { 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),
+ },
+ {
+ .ti = { 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),
+ },
+ {
+ .ti = { 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[] = {
+ {
+ .ti = { 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),
+ },
+ {
+ .ti = { 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),
+ },
+ {
+ .ti = { 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),
+ },
+ {
+ .ti = { 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),
+ },
+ {
+ .ti = { 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),
+ },
+ {
+ .ti = { 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/libosmo-gtlv/gtlv_test.c b/tests/libosmo-gtlv/gtlv_test.c
new file mode 100644
index 0000000..8023699
--- /dev/null
+++ b/tests/libosmo-gtlv/gtlv_test.c
@@ -0,0 +1,629 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gtlv/gtlv.h>
+
+void *ctx;
+
+struct ie {
+ struct osmo_gtlv_tag_inst ti;
+ const char *val;
+};
+
+/* write all IEs to a msgb */
+struct msgb *test_tlv_enc(const struct osmo_gtlv_cfg *cfg, const struct ie *ies)
+{
+ const struct ie *ie;
+ struct osmo_gtlv_put gtlv = {
+ .cfg = cfg,
+ .dst = msgb_alloc(1024, __func__),
+ };
+
+ for (ie = ies; ie->val; ie++) {
+ /* put header without knowing length yet */
+ OSMO_ASSERT(osmo_gtlv_put_tli(>lv, &ie->ti, 0) == 0);
+ /* put value data, as much as desired */
+ msgb_put(gtlv.dst, osmo_hexparse(ie->val, gtlv.dst->tail,
msgb_tailroom(gtlv.dst)));
+ /* update header len from amount of written data */
+ OSMO_ASSERT(osmo_gtlv_put_update_tl(>lv) == 0);
+ }
+
+ printf("- encoded: %s.\n", osmo_hexdump(gtlv.dst->data,
gtlv.dst->len));
+ return gtlv.dst;
+}
+
+/* read all IEs from the msgb, and verify that it matches the given list of IEs */
+void test_tlv_dec(const struct osmo_gtlv_cfg *cfg, const struct ie *ies, struct msgb
*msg)
+{
+ const struct ie *ie;
+ struct osmo_gtlv_load gtlv = {
+ .cfg = cfg,
+ .src = { msg->data, msg->len },
+ };
+
+ printf("- decoding:\n");
+ osmo_gtlv_load_start(>lv);
+
+ for (ie = ies; ie->val; ie++) {
+ int rc = osmo_gtlv_load_next(>lv);
+ if (rc) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next() rc = %d\n",
rc);
+ exit(1);
+ }
+ /* end of TLV structure? */
+ if (!gtlv.val)
+ break;
+ printf(" T=%s L=%zu", osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
gtlv.len);
+ printf(" v=%s\n", osmo_hexdump_nospc(gtlv.val, gtlv.len));
+ if (gtlv.ti.tag != ie->ti.tag) {
+ printf(" ERROR loading TLV structure: expected tag %u, got tag %u\n",
ie->ti.tag, gtlv.ti.tag);
+ exit(1);
+ }
+ if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) {
+ printf(" ERROR loading TLV structure: expected val %s, got val %s\n",
ie->val,
+ osmo_hexdump_nospc(gtlv.val, gtlv.len));
+ exit(1);
+ }
+ }
+}
+
+void test_tlv_peek(const struct osmo_gtlv_cfg *cfg, const struct ie *ies, struct msgb
*msg)
+{
+ const struct ie *ie;
+ struct osmo_gtlv_load gtlv = {
+ .cfg = cfg,
+ .src = { msg->data, msg->len },
+ };
+
+ printf("- peeking:\n");
+ osmo_gtlv_load_start(>lv);
+
+ ie = ies;
+ while (1) {
+ int rc;
+ struct osmo_gtlv_tag_inst next_tag;
+ rc = osmo_gtlv_load_peek_tag(>lv, &next_tag);
+ if (rc == -ENOENT) {
+ printf(" peek rc=-ENOENT\n");
+ } else {
+ printf(" peek T=%s", osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag,
NULL));
+ printf("\n");
+ }
+
+ if (ie->val && osmo_gtlv_tag_inst_cmp(&next_tag, &ie->ti)) {
+ printf(" ERROR peeking tag: expected tag %s, got tag %s\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL),
+ osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag, NULL));
+ exit(1);
+ }
+ if (!ie->val && rc != -ENOENT) {
+ printf(" ERROR peeking tag: expected -ENOENT, got rc=%d, tag %s\n", rc,
+ osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag, NULL));
+ exit(1);
+ }
+
+ if (rc == -ENOENT)
+ break;
+
+ /* go to the next TLV */
+ rc = osmo_gtlv_load_next(>lv);
+ if (rc) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next() rc = %d\n",
rc);
+ exit(1);
+ }
+ if (ie->val)
+ ie++;
+ }
+}
+
+/* Decode TLV in random order, each time searching for a tag in the raw data */
+void test_tlv_dec_by_tag(const struct osmo_gtlv_cfg *cfg, const struct ie *ies, struct
msgb *msg)
+{
+ const struct ie *last_ie;
+ const struct ie *ie;
+ int rc;
+ struct osmo_gtlv_load gtlv = {
+ .cfg = cfg,
+ .src = { msg->data, msg->len },
+ };
+
+ printf("- decoding in reverse order:\n");
+
+ last_ie = ies;
+ while (last_ie->val) last_ie++;
+ last_ie--;
+
+ for (ie = last_ie; ie >= ies; ie--) {
+ /* each time, look from the beginning */
+ osmo_gtlv_load_start(>lv);
+ rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
+ if (rc) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) rc =
%d\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), rc);
+ exit(1);
+ }
+ if (!gtlv.val) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s)
returned NULL val\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL));
+ exit(1);
+ }
+ if (osmo_gtlv_tag_inst_cmp(>lv.ti, &ie->ti)) {
+ printf(" ERROR loading TLV structure: expected tag %s, got tag %s\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL),
+ osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL));
+ exit(1);
+ }
+ if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) {
+ while (1) {
+ printf(" (mismatch: T=%s L=%zu v=%s, checking for another occurrence of
T=%s)\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
+ gtlv.len,
+ osmo_hexdump_nospc(gtlv.val, gtlv.len),
+ osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL));
+
+ rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
+ if (rc || !gtlv.val) {
+ printf(" ERROR val not found\n");
+ exit(1);
+ }
+ if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len)) == 0) {
+ break;
+ }
+ }
+ }
+ printf(" T=%s L=%zu v=%s\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
+ gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len));
+ }
+
+ printf("- decoding every second tag:\n");
+
+ osmo_gtlv_load_start(>lv);
+ for (ie = ies; ie->val; ie++) {
+ /* skip one tag */
+ ie++;
+ if (!ie->val)
+ break;
+
+ rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
+ if (rc) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) rc =
%d\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), rc);
+ exit(1);
+ }
+ if (!gtlv.val) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s)
returned NULL val\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL));
+ exit(1);
+ }
+ if (osmo_gtlv_tag_inst_cmp(>lv.ti, &ie->ti)) {
+ printf(" ERROR loading TLV structure: expected tag %s, got tag %s\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL),
+ osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL));
+ exit(1);
+ }
+ if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) {
+ while (1) {
+ printf(" (mismatch: T=%s L=%zu v=%s, checking for another occurrence of
T=%s)\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
+ gtlv.len,
+ osmo_hexdump_nospc(gtlv.val, gtlv.len),
+ osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL));
+
+ rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
+ if (rc || !gtlv.val) {
+ printf(" ERROR val not found\n");
+ exit(1);
+ }
+ if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len)) == 0) {
+ break;
+ }
+ }
+ }
+ printf(" T=%s L=%zu v=%s\n",
+ osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL),
+ gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len));
+ }
+
+ printf("- enforcing order: without restart, a past tag is not parsed
again:\n");
+ /* Try to read the first tag, expect that it isn't found because we're already
halfway in the message data */
+ ie = ies;
+ rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti);
+ printf(" osmo_gtlv_load_next_by_tag_inst(%s) rc=",
osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL));
+ if (rc == -ENOENT) {
+ printf("-ENOENT\n");
+ } else {
+ printf("%d\n", rc);
+ printf(" ERROR: expected -ENOENT\n");
+ exit(1);
+ }
+}
+
+void test_tlv(const char *label, struct ie *tests[], size_t tests_len, const struct
osmo_gtlv_cfg *cfg)
+{
+ int i;
+ for (i = 0; i < tests_len; i++) {
+ const struct ie *ies = tests[i];
+ struct msgb *msg;
+ printf("\n=== start: %s[%d]\n", label, i);
+
+ msg = test_tlv_enc(cfg, ies);
+ test_tlv_dec(cfg, ies, msg);
+ test_tlv_peek(cfg, ies, msg);
+ test_tlv_dec_by_tag(cfg, ies, msg);
+
+ msgb_free(msg);
+
+ printf("=== end: %s[%d]\n", label, i);
+ }
+}
+
+struct ie t8l8v_test1[] = {
+ /* smallest T */
+ { {}, "2342" },
+ /* largest T */
+ { {255}, "2342" },
+
+ /* smallest V (no V data) */
+ { {1}, "" },
+ /* largest V, 255 bytes is the largest that an 8bit size length can express. */
+ { {123}, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ },
+
+ /* arbitrary test data */
+ { {101}, "11" },
+ { {102}, "2222" },
+ { {103}, "333333" },
+ {}
+};
+
+struct ie t8l8v_test_multi[] = {
+ { {42}, "42" },
+ { {2}, "0101" },
+ { {2}, "2222" },
+ { {3}, "11" },
+ { {3}, "2222" },
+ { {3}, "333333" },
+ { {23}, "23" },
+ { {42}, "666f72747974776f" },
+ { {23}, "7477656e74797468726565" },
+ {}
+};
+
+struct ie *t8l8v_tests[] = {
+ t8l8v_test1,
+ t8l8v_test_multi,
+};
+
+void test_t8l8v()
+{
+ test_tlv(__func__, t8l8v_tests, ARRAY_SIZE(t8l8v_tests), &osmo_t8l8v_cfg);
+}
+
+struct ie t16l16v_test1[] = {
+ /* smallest T */
+ { {}, "2342" },
+ /* largest T */
+ { {65535}, "2342" },
+
+ /* smallest V (no V data) */
+ { {1}, "" },
+ /* 256 bytes is one more than an 8bit size length can express. */
+ { {123}, "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ },
+
+ /* arbitrary test data */
+ { {1001}, "11" },
+ { {1002}, "2222" },
+ { {1003}, "333333" },
+ {}
+};
+
+struct ie t16l16v_test_multi[] = {
+ { {1042}, "42" },
+ { {102}, "0101" },
+ { {102}, "2222" },
+ { {103}, "11" },
+ { {103}, "2222" },
+ { {103}, "333333" },
+ { {1023}, "23" },
+ { {1042}, "666f72747974776f" },
+ { {1023}, "7477656e74797468726565" },
+ {}
+};
+
+struct ie *t16l16v_tests[] = {
+ t16l16v_test1,
+ t16l16v_test_multi,
+};
+
+void test_t16l16v()
+{
+ test_tlv(__func__, t16l16v_tests, ARRAY_SIZE(t16l16v_tests), &osmo_t16l16v_cfg);
+}
+
+struct ie txlxv_test1[] = {
+ /* smallest T */
+ { {}, "2342" },
+ /* largest T that still fits in one encoded octet (highest bit serves as flag) */
+ { {0x7f}, "2342" },
+ /* smallest T that needs two octets to be encoded (first octet = 0x80 flag + 0, second
octet = 0x1) */
+ { {0x80}, "2342" },
+ /* largest T that can be encoded in 16bit - one flag bit. */
+ { {0x7fff}, "2342" },
+
+ /* smallest V (no V data) */
+ { {1}, "" },
+ /* 256 bytes is one more than an 8bit size length can express. */
+ { {123}, "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ },
+
+ /* arbitrary test data */
+ { {1002}, "2222" },
+ { {1003}, "333333" },
+ {}
+};
+
+struct ie txlxv_test_multi[] = {
+ { {1042}, "42" },
+ { {1002}, "0101" },
+ { {1002}, "2222" },
+ { {103}, "11" },
+ { {103}, "2222" },
+ { {103}, "333333" },
+ { {1023}, "23" },
+ { {1042}, "666f72747974776f" },
+ { {1023}, "7477656e74797468726565" },
+ {}
+};
+
+struct ie *txlxv_tests[] = {
+ txlxv_test1,
+ txlxv_test_multi,
+};
+
+/* Example of defining a variable TL, where size of T and L depend on the actual tag and
length values: load. */
+int txlxv_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t
src_data_len)
+{
+ const uint8_t *pos = src_data;
+ const uint8_t *end = src_data + src_data_len;
+ if (pos[0] & 0x80) {
+ if (pos + 2 > end)
+ return -EINVAL;
+ gtlv->ti.tag = (((int)pos[1]) << 7) + (pos[0] & 0x7f);
+ pos += 2;
+ } else {
+ gtlv->ti.tag = pos[0];
+ pos++;
+ }
+
+ switch (gtlv->ti.tag) {
+ case 1002:
+ /* fixed-length IE */
+ gtlv->len = 2;
+ break;
+ case 123:
+ /* 16bit length IE */
+ if (pos + 2 > end)
+ return -EINVAL;
+ gtlv->len = osmo_load16be(pos);
+ pos += 2;
+ break;
+ default:
+ /* 8bit length IE */
+ if (pos + 1 > end)
+ return -EINVAL;
+ gtlv->len = *pos;
+ pos++;
+ break;
+ }
+ gtlv->val = pos;
+ return 0;
+}
+
+/* Example of defining a variable TL, where size of T and L depend on the actual tag and
length values: store. */
+int txlxv_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct
osmo_gtlv_tag_inst *ti, size_t len,
+ struct osmo_gtlv_put *gtlv)
+{
+ uint8_t *pos = dst_data;
+ uint8_t *end = dst_data + dst_data_avail;
+ unsigned int tag = ti->tag;
+ if (tag < 0x80) {
+ if (pos + 1 > end)
+ return -ENOSPC;
+ pos[0] = tag;
+ pos++;
+ } else {
+ if (pos + 2 > end)
+ return -ENOSPC;
+ pos[0] = 0x80 + (tag & 0x7f);
+ pos[1] = tag >> 7;
+ pos += 2;
+ }
+
+ switch (tag) {
+ case 1002:
+ /* fixed-length IE, write no len */
+ break;
+ case 123:
+ /* 16bit length IE */
+ if (len > UINT16_MAX)
+ return -ERANGE;
+ if (pos + 2 > end)
+ return -ENOSPC;
+ osmo_store16be(len, pos);
+ pos += 2;
+ break;
+ default:
+ /* 8bit length IE */
+ if (len > UINT8_MAX)
+ return -ERANGE;
+ if (pos + 1 > end)
+ return -ENOSPC;
+ pos[0] = len;
+ pos++;
+ break;
+ }
+ return pos - dst_data;
+}
+
+const struct osmo_gtlv_cfg txlxv_cfg = {
+ .tl_min_size = 1,
+ .load_tl = txlxv_load_tl,
+ .store_tl = txlxv_store_tl,
+};
+
+void test_txlxv()
+{
+ test_tlv(__func__, txlxv_tests, ARRAY_SIZE(txlxv_tests), &txlxv_cfg);
+}
+
+/* Example of defining a TLI, with an instance indicator */
+static int tliv_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t
src_data_len)
+{
+ /* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 2.
*/
+ gtlv->ti.tag = src_data[0];
+ gtlv->len = src_data[1];
+
+ switch (gtlv->ti.tag) {
+ /* All tags that are TLIV go here */
+ case 5:
+ case 7:
+ case 9:
+ if (src_data_len < 3)
+ return -ENOSPC;
+ gtlv->ti.instance_present = true;
+ gtlv->ti.instance = src_data[2];
+ gtlv->val = src_data + 3;
+ return 0;
+ default:
+ gtlv->val = src_data + 2;
+ return 0;
+ }
+}
+
+static int tliv_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct
osmo_gtlv_tag_inst *ti, size_t len,
+ struct osmo_gtlv_put *gtlv)
+{
+ if (ti->tag > UINT8_MAX)
+ return -EINVAL;
+ if (len > UINT8_MAX)
+ return -EMSGSIZE;
+ if (dst_data_avail < 2)
+ return -ENOSPC;
+
+ dst_data[0] = ti->tag;
+ dst_data[1] = len;
+
+ switch (ti->tag) {
+ /* All tags that are TLIV go here */
+ case 5:
+ case 7:
+ case 9:
+ if (dst_data_avail < 3)
+ return -ENOSPC;
+ if (!ti->instance_present)
+ return -EINVAL;
+ if (ti->instance > UINT8_MAX)
+ return -EINVAL;
+ dst_data[2] = ti->instance;
+ return 3;
+ default:
+ return 2;
+ }
+}
+
+const struct osmo_gtlv_cfg osmo_tliv_cfg = {
+ .tl_min_size = 2,
+ .load_tl = tliv_load_tl,
+ .store_tl = tliv_store_tl,
+};
+
+struct ie tliv_test1[] = {
+ /* TLV */
+ { {1}, "0002" },
+ /* TLIV */
+ { {5, true, 1}, "0017" },
+ /* TLIV */
+ { {5, true, 2}, "0018" },
+ /* TLIV */
+ { {5, true, 3}, "0019" },
+ /* TLV */
+ { {6}, "001a" },
+ /* TLIV */
+ { {7, true, 1}, "001b" },
+ /* TLIV */
+ { {9, true, 1}, "001c" },
+ {}
+};
+
+struct ie *tliv_tests[] = {
+ tliv_test1,
+};
+
+void test_tliv()
+{
+ test_tlv(__func__, tliv_tests, ARRAY_SIZE(tliv_tests), &osmo_tliv_cfg);
+}
+
+int main()
+{
+ ctx = talloc_named_const(NULL, 0, "gtlv_test");
+ msgb_talloc_ctx_init(ctx, 0);
+
+ test_t8l8v();
+ test_t16l16v();
+ test_txlxv();
+ test_tliv();
+
+ talloc_free(ctx);
+ return 0;
+}
diff --git a/tests/libosmo-gtlv/gtlv_test.ok b/tests/libosmo-gtlv/gtlv_test.ok
new file mode 100644
index 0000000..edc6ff3
--- /dev/null
+++ b/tests/libosmo-gtlv/gtlv_test.ok
@@ -0,0 +1,291 @@
+
+=== start: test_t8l8v[0]
+- encoded: 00 02 23 42 ff 02 23 42 01 00 7b ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
ff 65 01 11 66 02 22 22 67 03 33 33 33 .
+- decoding:
+ T=0 L=2 v=2342
+ T=255 L=2 v=2342
+ T=1 L=0 v=
+ T=123 L=255
v=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ T=101 L=1 v=11
+ T=102 L=2 v=2222
+ T=103 L=3 v=333333
+- peeking:
+ peek T=0
+ peek T=255
+ peek T=1
+ peek T=123
+ peek T=101
+ peek T=102
+ peek T=103
+ peek rc=-ENOENT
+- decoding in reverse order:
+ T=103 L=3 v=333333
+ T=102 L=2 v=2222
+ T=101 L=1 v=11
+ T=123 L=255
v=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ T=1 L=0 v=
+ T=255 L=2 v=2342
+ T=0 L=2 v=2342
+- decoding every second tag:
+ T=255 L=2 v=2342
+ T=123 L=255
v=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ T=102 L=2 v=2222
+- enforcing order: without restart, a past tag is not parsed again:
+ osmo_gtlv_load_next_by_tag_inst(0) rc=-ENOENT
+=== end: test_t8l8v[0]
+
+=== start: test_t8l8v[1]
+- encoded: 2a 01 42 02 02 01 01 02 02 22 22 03 01 11 03 02 22 22 03 03 33 33 33 17 01 23
2a 08 66 6f 72 74 79 74 77 6f 17 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+- decoding:
+ T=42 L=1 v=42
+ T=2 L=2 v=0101
+ T=2 L=2 v=2222
+ T=3 L=1 v=11
+ T=3 L=2 v=2222
+ T=3 L=3 v=333333
+ T=23 L=1 v=23
+ T=42 L=8 v=666f72747974776f
+ T=23 L=11 v=7477656e74797468726565
+- peeking:
+ peek T=42
+ peek T=2
+ peek T=2
+ peek T=3
+ peek T=3
+ peek T=3
+ peek T=23
+ peek T=42
+ peek T=23
+ peek rc=-ENOENT
+- decoding in reverse order:
+ (mismatch: T=23 L=1 v=23, checking for another occurrence of T=23)
+ T=23 L=11 v=7477656e74797468726565
+ (mismatch: T=42 L=1 v=42, checking for another occurrence of T=42)
+ T=42 L=8 v=666f72747974776f
+ T=23 L=1 v=23
+ (mismatch: T=3 L=1 v=11, checking for another occurrence of T=3)
+ (mismatch: T=3 L=2 v=2222, checking for another occurrence of T=3)
+ T=3 L=3 v=333333
+ (mismatch: T=3 L=1 v=11, checking for another occurrence of T=3)
+ T=3 L=2 v=2222
+ T=3 L=1 v=11
+ (mismatch: T=2 L=2 v=0101, checking for another occurrence of T=2)
+ T=2 L=2 v=2222
+ T=2 L=2 v=0101
+ T=42 L=1 v=42
+- decoding every second tag:
+ T=2 L=2 v=0101
+ T=3 L=1 v=11
+ (mismatch: T=3 L=2 v=2222, checking for another occurrence of T=3)
+ T=3 L=3 v=333333
+ T=42 L=8 v=666f72747974776f
+- enforcing order: without restart, a past tag is not parsed again:
+ osmo_gtlv_load_next_by_tag_inst(42) rc=-ENOENT
+=== end: test_t8l8v[1]
+
+=== start: test_t16l16v[0]
+- encoded: 00 00 00 02 23 42 ff ff 00 02 23 42 00 01 00 00 00 7b 01 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 03 e9 00 01 11 03 ea 00 02 22 22 03 eb 00 03 33 33 33 .
+- decoding:
+ T=0 L=2 v=2342
+ T=65535 L=2 v=2342
+ T=1 L=0 v=
+ T=123 L=256
v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ T=1001 L=1 v=11
+ T=1002 L=2 v=2222
+ T=1003 L=3 v=333333
+- peeking:
+ peek T=0
+ peek T=65535
+ peek T=1
+ peek T=123
+ peek T=1001
+ peek T=1002
+ peek T=1003
+ peek rc=-ENOENT
+- decoding in reverse order:
+ T=1003 L=3 v=333333
+ T=1002 L=2 v=2222
+ T=1001 L=1 v=11
+ T=123 L=256
v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ T=1 L=0 v=
+ T=65535 L=2 v=2342
+ T=0 L=2 v=2342
+- decoding every second tag:
+ T=65535 L=2 v=2342
+ T=123 L=256
v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ T=1002 L=2 v=2222
+- enforcing order: without restart, a past tag is not parsed again:
+ osmo_gtlv_load_next_by_tag_inst(0) rc=-ENOENT
+=== end: test_t16l16v[0]
+
+=== start: test_t16l16v[1]
+- encoded: 04 12 00 01 42 00 66 00 02 01 01 00 66 00 02 22 22 00 67 00 01 11 00 67 00 02
22 22 00 67 00 03 33 33 33 03 ff 00 01 23 04 12 00 08 66 6f 72 74 79 74 77 6f 03 ff 00 0b
74 77 65 6e 74 79 74 68 72 65 65 .
+- decoding:
+ T=1042 L=1 v=42
+ T=102 L=2 v=0101
+ T=102 L=2 v=2222
+ T=103 L=1 v=11
+ T=103 L=2 v=2222
+ T=103 L=3 v=333333
+ T=1023 L=1 v=23
+ T=1042 L=8 v=666f72747974776f
+ T=1023 L=11 v=7477656e74797468726565
+- peeking:
+ peek T=1042
+ peek T=102
+ peek T=102
+ peek T=103
+ peek T=103
+ peek T=103
+ peek T=1023
+ peek T=1042
+ peek T=1023
+ peek rc=-ENOENT
+- decoding in reverse order:
+ (mismatch: T=1023 L=1 v=23, checking for another occurrence of T=1023)
+ T=1023 L=11 v=7477656e74797468726565
+ (mismatch: T=1042 L=1 v=42, checking for another occurrence of T=1042)
+ T=1042 L=8 v=666f72747974776f
+ T=1023 L=1 v=23
+ (mismatch: T=103 L=1 v=11, checking for another occurrence of T=103)
+ (mismatch: T=103 L=2 v=2222, checking for another occurrence of T=103)
+ T=103 L=3 v=333333
+ (mismatch: T=103 L=1 v=11, checking for another occurrence of T=103)
+ T=103 L=2 v=2222
+ T=103 L=1 v=11
+ (mismatch: T=102 L=2 v=0101, checking for another occurrence of T=102)
+ T=102 L=2 v=2222
+ T=102 L=2 v=0101
+ T=1042 L=1 v=42
+- decoding every second tag:
+ T=102 L=2 v=0101
+ T=103 L=1 v=11
+ (mismatch: T=103 L=2 v=2222, checking for another occurrence of T=103)
+ T=103 L=3 v=333333
+ T=1042 L=8 v=666f72747974776f
+- enforcing order: without restart, a past tag is not parsed again:
+ osmo_gtlv_load_next_by_tag_inst(1042) rc=-ENOENT
+=== end: test_t16l16v[1]
+
+=== start: test_txlxv[0]
+- encoded: 00 02 23 42 7f 02 23 42 80 01 02 23 42 ff ff 02 23 42 01 00 7b 01 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 ea 07 22 22 eb 07 03 33 33 33 .
+- decoding:
+ T=0 L=2 v=2342
+ T=127 L=2 v=2342
+ T=128 L=2 v=2342
+ T=32767 L=2 v=2342
+ T=1 L=0 v=
+ T=123 L=256
v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ T=1002 L=2 v=2222
+ T=1003 L=3 v=333333
+- peeking:
+ peek T=0
+ peek T=127
+ peek T=128
+ peek T=32767
+ peek T=1
+ peek T=123
+ peek T=1002
+ peek T=1003
+ peek rc=-ENOENT
+- decoding in reverse order:
+ T=1003 L=3 v=333333
+ T=1002 L=2 v=2222
+ T=123 L=256
v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ T=1 L=0 v=
+ T=32767 L=2 v=2342
+ T=128 L=2 v=2342
+ T=127 L=2 v=2342
+ T=0 L=2 v=2342
+- decoding every second tag:
+ T=127 L=2 v=2342
+ T=32767 L=2 v=2342
+ T=123 L=256
v=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ T=1003 L=3 v=333333
+- enforcing order: without restart, a past tag is not parsed again:
+ osmo_gtlv_load_next_by_tag_inst(0) rc=-ENOENT
+=== end: test_txlxv[0]
+
+=== start: test_txlxv[1]
+- encoded: 92 08 01 42 ea 07 01 01 ea 07 22 22 67 01 11 67 02 22 22 67 03 33 33 33 ff 07
01 23 92 08 08 66 6f 72 74 79 74 77 6f ff 07 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+- decoding:
+ T=1042 L=1 v=42
+ T=1002 L=2 v=0101
+ T=1002 L=2 v=2222
+ T=103 L=1 v=11
+ T=103 L=2 v=2222
+ T=103 L=3 v=333333
+ T=1023 L=1 v=23
+ T=1042 L=8 v=666f72747974776f
+ T=1023 L=11 v=7477656e74797468726565
+- peeking:
+ peek T=1042
+ peek T=1002
+ peek T=1002
+ peek T=103
+ peek T=103
+ peek T=103
+ peek T=1023
+ peek T=1042
+ peek T=1023
+ peek rc=-ENOENT
+- decoding in reverse order:
+ (mismatch: T=1023 L=1 v=23, checking for another occurrence of T=1023)
+ T=1023 L=11 v=7477656e74797468726565
+ (mismatch: T=1042 L=1 v=42, checking for another occurrence of T=1042)
+ T=1042 L=8 v=666f72747974776f
+ T=1023 L=1 v=23
+ (mismatch: T=103 L=1 v=11, checking for another occurrence of T=103)
+ (mismatch: T=103 L=2 v=2222, checking for another occurrence of T=103)
+ T=103 L=3 v=333333
+ (mismatch: T=103 L=1 v=11, checking for another occurrence of T=103)
+ T=103 L=2 v=2222
+ T=103 L=1 v=11
+ (mismatch: T=1002 L=2 v=0101, checking for another occurrence of T=1002)
+ T=1002 L=2 v=2222
+ T=1002 L=2 v=0101
+ T=1042 L=1 v=42
+- decoding every second tag:
+ T=1002 L=2 v=0101
+ T=103 L=1 v=11
+ (mismatch: T=103 L=2 v=2222, checking for another occurrence of T=103)
+ T=103 L=3 v=333333
+ T=1042 L=8 v=666f72747974776f
+- enforcing order: without restart, a past tag is not parsed again:
+ osmo_gtlv_load_next_by_tag_inst(1042) rc=-ENOENT
+=== end: test_txlxv[1]
+
+=== start: test_tliv[0]
+- encoded: 01 02 00 02 05 02 01 00 17 05 02 02 00 18 05 02 03 00 19 06 02 00 1a 07 02 01
00 1b 09 02 01 00 1c .
+- decoding:
+ T=1 L=2 v=0002
+ T=5[1] L=2 v=0017
+ T=5[2] L=2 v=0018
+ T=5[3] L=2 v=0019
+ T=6 L=2 v=001a
+ T=7[1] L=2 v=001b
+ T=9[1] L=2 v=001c
+- peeking:
+ peek T=1
+ peek T=5[1]
+ peek T=5[2]
+ peek T=5[3]
+ peek T=6
+ peek T=7[1]
+ peek T=9[1]
+ peek rc=-ENOENT
+- decoding in reverse order:
+ T=9[1] L=2 v=001c
+ T=7[1] L=2 v=001b
+ T=6 L=2 v=001a
+ T=5[3] L=2 v=0019
+ T=5[2] L=2 v=0018
+ T=5[1] L=2 v=0017
+ T=1 L=2 v=0002
+- decoding every second tag:
+ T=5[1] L=2 v=0017
+ T=5[3] L=2 v=0019
+ T=7[1] L=2 v=001b
+- enforcing order: without restart, a past tag is not parsed again:
+ osmo_gtlv_load_next_by_tag_inst(1) rc=-ENOENT
+=== end: test_tliv[0]
diff --git a/tests/libosmo-gtlv/test_gtlv_gen/Makefile.am
b/tests/libosmo-gtlv/test_gtlv_gen/Makefile.am
new file mode 100644
index 0000000..efb6d17
--- /dev/null
+++ b/tests/libosmo-gtlv/test_gtlv_gen/Makefile.am
@@ -0,0 +1,64 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ -I$(bulddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gen__myproto_ies_auto \
+ gtlv_gen_test \
+ $(NULL)
+
+EXTRA_DIST = \
+ myproto_ies_custom.h \
+ gtlv_gen_test.ok \
+ $(NULL)
+
+BUILT_SOURCES = \
+ myproto_ies_auto.h \
+ myproto_ies_auto.c \
+ $(NULL)
+
+CLEANFILES = \
+ myproto_ies_auto.h \
+ myproto_ies_auto.c \
+ $(NULL)
+
+gen__myproto_ies_auto_SOURCES = \
+ gen__myproto_ies_auto.c \
+ myproto_ies_custom.c \
+ $(NULL)
+
+gen__myproto_ies_auto_LDADD = \
+ $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.la \
+ $(TALLOC_LIBS) \
+ $(PTHREAD_LIBS) \
+ $(NULL)
+
+myproto_ies_auto.h: $(builddir)/gen__myproto_ies_auto
+ $(builddir)/gen__myproto_ies_auto h > $(builddir)/myproto_ies_auto.h
+myproto_ies_auto.c: $(builddir)/gen__myproto_ies_auto
+ $(builddir)/gen__myproto_ies_auto c > $(builddir)/myproto_ies_auto.c
+
+gtlv_gen_test_SOURCES = \
+ gtlv_gen_test.c \
+ myproto_ies_custom.c \
+ myproto_ies_auto.c \
+ $(NULL)
+
+gtlv_gen_test_LDADD = \
+ $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.la \
+ $(TALLOC_LIBS) \
+ $(PTHREAD_LIBS) \
+ $(NULL)
+
+.PHONY: update_exp
+update_exp:
+ $(builddir)/gtlv_gen_test >$(srcdir)/gtlv_gen_test.ok
diff --git a/tests/libosmo-gtlv/test_gtlv_gen/gen__myproto_ies_auto.c
b/tests/libosmo-gtlv/test_gtlv_gen/gen__myproto_ies_auto.c
new file mode 100644
index 0000000..75d657d
--- /dev/null
+++ b/tests/libosmo-gtlv/test_gtlv_gen/gen__myproto_ies_auto.c
@@ -0,0 +1,114 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <stdbool.h>
+#include <stdio.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gtlv/gtlv_gen.h>
+
+#define O OSMO_GTLV_GEN_O
+#define M OSMO_GTLV_GEN_M
+#define O_MULTI OSMO_GTLV_GEN_O_MULTI
+#define M_MULTI OSMO_GTLV_GEN_M_MULTI
+
+#define ALL_FROM_NAME osmo_gtlv_gen_ie_auto
+
+/* An IE where the type is not a 'struct myproto_ie_${name}'. */
+static const struct osmo_gtlv_gen_ie number = {
+ .decoded_type = "int", /* add 'int foo;' to the struct */
+ .dec_enc = "u16", /* use myproto_dec_u16() and myproto_enc_u16() for the TLV
value part */
+ .spec_ref = "an int coded as uint16_t",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_moo_nest[] = {
+ /* Mandatory member xxx.foo of the type defined in 'number' above. */
+ M(number, "foo"),
+ /* Mandatory member xxx.bar of type 'struct myproto_ie_bar', using
myproto_ie_dec_bar(), myproto_ie_enc_bar(),
+ * myproto_ie_enc_to_str_bar(), all defined in myproto_ies_custom.h/c. */
+ M(ALL_FROM_NAME, "bar"),
+ M(ALL_FROM_NAME, "baz"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie huge_number = {
+ .decoded_type = "uint64_t",
+ .dec_enc = "u64",
+};
+
+static const struct osmo_gtlv_gen_ie moo_nest = {
+ .tag_name = "moo_nest",
+ .nested_ies = ies_in_moo_nest,
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_goo_nest[] = {
+ O(huge_number, "val"),
+ M(moo_nest, "nest"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie goo_nest = {
+ .tag_name = "goo_nest",
+ .nested_ies = ies_in_goo_nest,
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_moo_msg[] = {
+ M(number, "foo"),
+ M(ALL_FROM_NAME, "bar"),
+ O(ALL_FROM_NAME, "baz"),
+ O_MULTI(32, number, "repeat_int"),
+ O_MULTI(32, ALL_FROM_NAME, "repeat_struct"),
+ O(moo_nest, "nest"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_goo_msg[] = {
+ M(number, "foo"),
+ O(ALL_FROM_NAME, "bar"),
+ O_MULTI(8, goo_nest, "nest"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_msg msg_defs[] = {
+ { "moo", ies_in_moo_msg },
+ { "goo", ies_in_goo_msg },
+ {}
+};
+
+int main(int argc, const char **argv)
+{
+ struct osmo_gtlv_gen_cfg cfg = {
+ .proto_name = "myproto",
+ .message_type_enum = "enum myproto_msg_type",
+ .message_type_prefix = "MYPROTO_MSGT_",
+ .tag_enum = "enum myproto_iei",
+ .tag_prefix = "MYPROTO_IEI_",
+ .decoded_type_prefix = "struct myproto_ie_",
+ .h_header = "#include \"myproto_ies_custom.h\"",
+ .c_header = "#include <myproto_ies_auto.h>",
+ .msg_defs = msg_defs,
+ .add_enc_to_str = true,
+ };
+ return osmo_gtlv_gen_main(&cfg, argc, argv);
+}
diff --git a/tests/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test.c
b/tests/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test.c
new file mode 100644
index 0000000..ef5372c
--- /dev/null
+++ b/tests/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test.c
@@ -0,0 +1,261 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gtlv/gtlv.h>
+
+#include <myproto_ies_auto.h>
+
+struct myproto_msg {
+ enum myproto_msg_type type;
+ union myproto_ies ies;
+};
+
+static void err_cb(void *data, void *decoded_struct, const char *file, int line, const
char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ //printf("ERR: %s:%d ", file, line);
+ printf("ERR: ");
+ vprintf(fmt, args);
+ va_end(args);
+}
+
+static int myproto_msg_enc(struct msgb *dst, const struct myproto_msg *msg, const struct
osmo_gtlv_cfg *cfg)
+{
+ struct osmo_gtlv_put gtlv = {
+ .cfg = cfg,
+ .dst = dst,
+ };
+
+ msgb_put_u8(gtlv.dst, msg->type);
+ return myproto_ies_encode(>lv, (void *)&msg->ies, msg->type, err_cb,
NULL, myproto_iei_names);
+}
+
+static int myproto_msg_dec(struct myproto_msg *msg, const uint8_t *data, size_t
data_len,
+ const struct osmo_gtlv_cfg *cfg, bool ordered)
+{
+ struct osmo_gtlv_load gtlv;
+ if (data_len < 1)
+ return -EINVAL;
+ msg->type = data[0];
+ gtlv = (struct osmo_gtlv_load){
+ .cfg = cfg,
+ .src = { data + 1, data_len - 1 },
+ };
+ return myproto_ies_decode(&msg->ies, >lv, ordered, msg->type, err_cb,
NULL, myproto_iei_names);
+}
+
+void *ctx;
+
+struct myproto_msg tests[] = {
+ {
+ MYPROTO_MSGT_MOO,
+ {
+ .moo = {
+ .foo = 23,
+ .bar = { "twentythree" },
+ },
+ },
+ },
+ {
+ MYPROTO_MSGT_MOO,
+ {
+ .moo = {
+ .foo = 23,
+ .bar = { "twentythree" },
+
+ .baz_present = true,
+ .baz = {
+ .v_int = 2323,
+ .v_bool = true,
+ },
+ },
+ },
+ },
+ {
+ MYPROTO_MSGT_MOO,
+ {
+ .moo = {
+ .foo = 23,
+ .bar = { "twentythree" },
+
+ .baz_present = true,
+ .baz = {
+ .v_int = 2323,
+ .v_bool = true,
+ },
+
+ .repeat_int_count = 3,
+ .repeat_int = { 1, 2, 0x7fff },
+ },
+ },
+ },
+ {
+ MYPROTO_MSGT_MOO,
+ {
+ .moo = {
+ .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,
+ },
+ },
+ },
+ },
+ },
+ {
+ MYPROTO_MSGT_GOO,
+ {
+ .goo = {
+ .foo = 17,
+
+ .bar_present = true,
+ .bar = { "gooei" },
+
+ .nest_count = 2,
+ .nest = {
+ {
+ .val_present = true,
+ .val = 0x0123456789abcdef,
+ .nest = {
+ .foo = 11,
+ .bar = { "eleven" },
+ .baz = {
+ .v_int = 1111,
+ .v_bool = true,
+ },
+ },
+ },
+ {
+ .val_present = false,
+ .nest = {
+ .foo = 12,
+ .bar = { "twelve" },
+ .baz = {
+ .v_int = 1212,
+ .v_bool = false,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+int myproto_msg_to_str_buf(char *buf, size_t buflen, const struct myproto_msg *m)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "%s={", get_value_string(myproto_msg_type_names,
m->type));
+ OSMO_STRBUF_APPEND(sb, osmo_gtlvs_encode_to_str_buf, &m->ies, 0,
myproto_get_msg_coding(m->type),
+ myproto_iei_names);
+ OSMO_STRBUF_PRINTF(sb, " }");
+ return sb.chars_needed;
+
+}
+
+char *myproto_msg_to_str(const struct myproto_msg *m)
+{
+ OSMO_NAME_C_IMPL(ctx, 256, "ERROR", myproto_msg_to_str_buf, m)
+}
+
+void test_enc_dec(const char *label, const struct osmo_gtlv_cfg *cfg, bool ordered)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ int rc;
+ const struct myproto_msg *orig = &tests[i];
+ struct myproto_msg parsed = {};
+ struct msgb *msg;
+
+ printf("\n=== start %s %s[%d]\n", label, __func__, i);
+ printf("encoded: %s\n", myproto_msg_to_str(orig));
+
+ msg = msgb_alloc(1024, __func__);
+ rc = myproto_msg_enc(msg, orig, cfg);
+ printf("myproto_msg_enc() rc = %d\n", rc);
+ printf("%s.\n", osmo_hexdump(msg->data, msg->len));
+
+ rc = myproto_msg_dec(&parsed, msg->data, msg->len, cfg, ordered);
+ printf("myproto_msg_dec() rc = %d\n", rc);
+ printf("decoded: %s\n", myproto_msg_to_str(&parsed));
+ if (strcmp(myproto_msg_to_str(orig), myproto_msg_to_str(&parsed))) {
+ printf(" ERROR: parsed != orig\n");
+ exit(1);
+ }
+
+ msgb_free(msg);
+ printf("=== end %s %s[%d]\n", label, __func__, i);
+ }
+}
+
+int main()
+{
+ ctx = talloc_named_const(NULL, 0, "test_gen_tlv");
+ 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/test_gtlv_gen/gtlv_gen_test.ok
b/tests/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test.ok
new file mode 100644
index 0000000..e178831
--- /dev/null
+++ b/tests/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test.ok
@@ -0,0 +1,160 @@
+
+=== start t8l8v ordered test_enc_dec[0]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree" }
+myproto_msg_enc() rc = 0
+01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree" }
+=== end t8l8v ordered test_enc_dec[0]
+
+=== start t8l8v ordered test_enc_dec[1]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} }
+myproto_msg_enc() rc = 0
+01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} }
+=== end t8l8v ordered test_enc_dec[1]
+
+=== start t8l8v ordered test_enc_dec[2]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ '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: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={
{1001,true,R_A}, {1002,false,R_B} } 'MOO_NEST'={ 'FOO'=42
'BAR'="fortytwo" 'BAZ'={4242,false} } }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={
{1001,true,R_A}, {1002,false,R_B} } 'MOO_NEST'={ 'FOO'=42
'BAR'="fortytwo" 'BAZ'={4242,false} } }
+=== end t8l8v ordered test_enc_dec[3]
+
+=== start t8l8v ordered test_enc_dec[4]
+encoded: GOO={ 'FOO'=17 'BAR'="gooei" 'GOO_NEST'={ {
'VAL'=0x123456789abcdef 'MOO_NEST'={ 'FOO'=11
'BAR'="eleven" 'BAZ'={1111,true} } }, { 'MOO_NEST'={
'FOO'=12 'BAR'="twelve" 'BAZ'={1212,false} } } } }
+myproto_msg_enc() rc = 0
+07 01 02 00 11 02 05 67 6f 6f 65 69 08 1c 07 08 01 23 45 67 89 ab cd ef 06 10 01 02 00 0b
02 06 65 6c 65 76 65 6e 03 02 84 57 08 12 06 10 01 02 00 0c 02 06 74 77 65 6c 76 65 03 02
04 bc .
+myproto_msg_dec() rc = 0
+decoded: GOO={ 'FOO'=17 'BAR'="gooei" 'GOO_NEST'={ {
'VAL'=0x123456789abcdef 'MOO_NEST'={ 'FOO'=11
'BAR'="eleven" 'BAZ'={1111,true} } }, { 'MOO_NEST'={
'FOO'=12 'BAR'="twelve" 'BAZ'={1212,false} } } } }
+=== end t8l8v ordered test_enc_dec[4]
+
+=== start t8l8v unordered test_enc_dec[0]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree" }
+myproto_msg_enc() rc = 0
+01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree" }
+=== end t8l8v unordered test_enc_dec[0]
+
+=== start t8l8v unordered test_enc_dec[1]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} }
+myproto_msg_enc() rc = 0
+01 01 02 00 17 02 0b 74 77 65 6e 74 79 74 68 72 65 65 03 02 89 13 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} }
+=== end t8l8v unordered test_enc_dec[1]
+
+=== start t8l8v unordered test_enc_dec[2]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ '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: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={
{1001,true,R_A}, {1002,false,R_B} } 'MOO_NEST'={ 'FOO'=42
'BAR'="fortytwo" 'BAZ'={4242,false} } }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={
{1001,true,R_A}, {1002,false,R_B} } 'MOO_NEST'={ 'FOO'=42
'BAR'="fortytwo" 'BAZ'={4242,false} } }
+=== end t8l8v unordered test_enc_dec[3]
+
+=== start t8l8v unordered test_enc_dec[4]
+encoded: GOO={ 'FOO'=17 'BAR'="gooei" 'GOO_NEST'={ {
'VAL'=0x123456789abcdef 'MOO_NEST'={ 'FOO'=11
'BAR'="eleven" 'BAZ'={1111,true} } }, { 'MOO_NEST'={
'FOO'=12 'BAR'="twelve" 'BAZ'={1212,false} } } } }
+myproto_msg_enc() rc = 0
+07 01 02 00 11 02 05 67 6f 6f 65 69 08 1c 07 08 01 23 45 67 89 ab cd ef 06 10 01 02 00 0b
02 06 65 6c 65 76 65 6e 03 02 84 57 08 12 06 10 01 02 00 0c 02 06 74 77 65 6c 76 65 03 02
04 bc .
+myproto_msg_dec() rc = 0
+decoded: GOO={ 'FOO'=17 'BAR'="gooei" 'GOO_NEST'={ {
'VAL'=0x123456789abcdef 'MOO_NEST'={ 'FOO'=11
'BAR'="eleven" 'BAZ'={1111,true} } }, { 'MOO_NEST'={
'FOO'=12 'BAR'="twelve" 'BAZ'={1212,false} } } } }
+=== end t8l8v unordered test_enc_dec[4]
+
+=== start t16l16v ordered test_enc_dec[0]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree" }
+myproto_msg_enc() rc = 0
+01 00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree" }
+=== end t16l16v ordered test_enc_dec[0]
+
+=== start t16l16v ordered test_enc_dec[1]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} }
+=== end t16l16v ordered test_enc_dec[1]
+
+=== start t16l16v ordered test_enc_dec[2]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ '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: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={
{1001,true,R_A}, {1002,false,R_B} } 'MOO_NEST'={ 'FOO'=42
'BAR'="fortytwo" 'BAZ'={4242,false} } }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={
{1001,true,R_A}, {1002,false,R_B} } 'MOO_NEST'={ 'FOO'=42
'BAR'="fortytwo" 'BAZ'={4242,false} } }
+=== end t16l16v ordered test_enc_dec[3]
+
+=== start t16l16v ordered test_enc_dec[4]
+encoded: GOO={ 'FOO'=17 'BAR'="gooei" 'GOO_NEST'={ {
'VAL'=0x123456789abcdef 'MOO_NEST'={ 'FOO'=11
'BAR'="eleven" 'BAZ'={1111,true} } }, { 'MOO_NEST'={
'FOO'=12 'BAR'="twelve" 'BAZ'={1212,false} } } } }
+myproto_msg_enc() rc = 0
+07 00 01 00 02 00 11 00 02 00 05 67 6f 6f 65 69 00 08 00 26 00 07 00 08 01 23 45 67 89 ab
cd ef 00 06 00 16 00 01 00 02 00 0b 00 02 00 06 65 6c 65 76 65 6e 00 03 00 02 84 57 00 08
00 1a 00 06 00 16 00 01 00 02 00 0c 00 02 00 06 74 77 65 6c 76 65 00 03 00 02 04 bc .
+myproto_msg_dec() rc = 0
+decoded: GOO={ 'FOO'=17 'BAR'="gooei" 'GOO_NEST'={ {
'VAL'=0x123456789abcdef 'MOO_NEST'={ 'FOO'=11
'BAR'="eleven" 'BAZ'={1111,true} } }, { 'MOO_NEST'={
'FOO'=12 'BAR'="twelve" 'BAZ'={1212,false} } } } }
+=== end t16l16v ordered test_enc_dec[4]
+
+=== start t16l16v unordered test_enc_dec[0]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree" }
+myproto_msg_enc() rc = 0
+01 00 01 00 02 00 17 00 02 00 0b 74 77 65 6e 74 79 74 68 72 65 65 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree" }
+=== end t16l16v unordered test_enc_dec[0]
+
+=== start t16l16v unordered test_enc_dec[1]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} }
+=== end t16l16v unordered test_enc_dec[1]
+
+=== start t16l16v unordered test_enc_dec[2]
+encoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ '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: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={
{1001,true,R_A}, {1002,false,R_B} } 'MOO_NEST'={ 'FOO'=42
'BAR'="fortytwo" 'BAZ'={4242,false} } }
+myproto_msg_enc() rc = 0
+01 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 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'FOO'=23 'BAR'="twentythree"
'BAZ'={2323,true} 'REPEAT_INT'={ 1, 2, 32767 } 'REPEAT_STRUCT'={
{1001,true,R_A}, {1002,false,R_B} } 'MOO_NEST'={ 'FOO'=42
'BAR'="fortytwo" 'BAZ'={4242,false} } }
+=== end t16l16v unordered test_enc_dec[3]
+
+=== start t16l16v unordered test_enc_dec[4]
+encoded: GOO={ 'FOO'=17 'BAR'="gooei" 'GOO_NEST'={ {
'VAL'=0x123456789abcdef 'MOO_NEST'={ 'FOO'=11
'BAR'="eleven" 'BAZ'={1111,true} } }, { 'MOO_NEST'={
'FOO'=12 'BAR'="twelve" 'BAZ'={1212,false} } } } }
+myproto_msg_enc() rc = 0
+07 00 01 00 02 00 11 00 02 00 05 67 6f 6f 65 69 00 08 00 26 00 07 00 08 01 23 45 67 89 ab
cd ef 00 06 00 16 00 01 00 02 00 0b 00 02 00 06 65 6c 65 76 65 6e 00 03 00 02 84 57 00 08
00 1a 00 06 00 16 00 01 00 02 00 0c 00 02 00 06 74 77 65 6c 76 65 00 03 00 02 04 bc .
+myproto_msg_dec() rc = 0
+decoded: GOO={ 'FOO'=17 'BAR'="gooei" 'GOO_NEST'={ {
'VAL'=0x123456789abcdef 'MOO_NEST'={ 'FOO'=11
'BAR'="eleven" 'BAZ'={1111,true} } }, { 'MOO_NEST'={
'FOO'=12 'BAR'="twelve" 'BAZ'={1212,false} } } } }
+=== end t16l16v unordered test_enc_dec[4]
diff --git a/tests/libosmo-gtlv/test_gtlv_gen/myproto_ies_custom.c
b/tests/libosmo-gtlv/test_gtlv_gen/myproto_ies_custom.c
new file mode 100644
index 0000000..8548165
--- /dev/null
+++ b/tests/libosmo-gtlv/test_gtlv_gen/myproto_ies_custom.c
@@ -0,0 +1,180 @@
+/* Example for defining custom IES for gtlv_gen. */
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <inttypes.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gtlv/gtlv.h>
+
+#include <myproto_ies_custom.h>
+
+int myproto_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 myproto_enc_u16(struct osmo_gtlv_put *gtlv, void *decoded_struct, void *encode_from)
+{
+ int *foo = encode_from;
+ if (*foo > INT16_MAX)
+ return -EINVAL;
+ msgb_put_u16(gtlv->dst, *foo);
+ return 0;
+}
+
+int myproto_enc_to_str_u16(char *buf, size_t buflen, void *encode_from)
+{
+ int *foo = encode_from;
+ return snprintf(buf, buflen, "%d", *foo);
+}
+
+int myproto_dec_u64(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load
*gtlv)
+{
+ uint64_t *val = decode_to;
+ if (gtlv->len != sizeof(uint64_t))
+ return -EINVAL;
+ *val = osmo_load64be(gtlv->val);
+ return 0;
+}
+
+int myproto_enc_u64(struct osmo_gtlv_put *gtlv, void *decoded_struct, void *encode_from)
+{
+ uint64_t *val = encode_from;
+ osmo_store64be(*val, msgb_put(gtlv->dst, sizeof(*val)));
+ return 0;
+}
+
+int myproto_enc_to_str_u64(char *buf, size_t buflen, void *encode_from)
+{
+ uint64_t *val = encode_from;
+ return snprintf(buf, buflen, "0x%"PRIx64, *val);
+}
+
+int myproto_dec_bar(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load
*gtlv)
+{
+ struct myproto_ie_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 myproto_enc_bar(struct osmo_gtlv_put *gtlv, void *decoded_struct, void *encode_from)
+{
+ struct myproto_ie_bar *bar = encode_from;
+ int len = strnlen(bar->str, sizeof(bar->str));
+ memcpy(msgb_put(gtlv->dst, len), bar, len);
+ return 0;
+}
+
+int myproto_enc_to_str_bar(char *buf, size_t buflen, void *encode_from)
+{
+ struct myproto_ie_bar *bar = encode_from;
+ return osmo_quote_str_buf3(buf, buflen, bar->str, -1);
+}
+
+int myproto_dec_baz(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load
*gtlv)
+{
+ struct myproto_ie_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 myproto_enc_baz(struct osmo_gtlv_put *gtlv, void *decoded_struct, void *encode_from)
+{
+ struct myproto_ie_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 myproto_enc_to_str_baz(char *buf, size_t buflen, void *encode_from)
+{
+ struct myproto_ie_baz *baz = encode_from;
+ return snprintf(buf, buflen, "{%d,%s}", baz->v_int, baz->v_bool ?
"true" : "false");
+}
+
+int myproto_dec_repeat_struct(void *decoded_struct, void *decode_to, const struct
osmo_gtlv_load *gtlv)
+{
+ struct myproto_ie_repeat_struct *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 myproto_enc_repeat_struct(struct osmo_gtlv_put *gtlv, void *decoded_struct, void
*encode_from)
+{
+ struct myproto_ie_repeat_struct *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 myproto_enc_to_str_repeat_struct(char *buf, size_t buflen, void *encode_from)
+{
+ struct myproto_ie_repeat_struct *repeat_struct = encode_from;
+ return snprintf(buf, buflen, "{%d,%s,%s}",
+ repeat_struct->v_int, repeat_struct->v_bool ? "true" :
"false",
+ get_value_string(myproto_repeat_enum_names, repeat_struct->v_enum));
+}
+
+const struct value_string myproto_msg_type_names[] = {
+ { MYPROTO_MSGT_MOO, "MOO" },
+ { MYPROTO_MSGT_GOO, "GOO" },
+ {}
+};
+
+const struct value_string myproto_iei_names[] = {
+ { MYPROTO_IEI_FOO, "FOO" },
+ { MYPROTO_IEI_BAR, "BAR" },
+ { MYPROTO_IEI_BAZ, "BAZ" },
+ { MYPROTO_IEI_REPEAT_INT, "REPEAT_INT" },
+ { MYPROTO_IEI_REPEAT_STRUCT, "REPEAT_STRUCT" },
+ { MYPROTO_IEI_MOO_NEST, "MOO_NEST" },
+ { MYPROTO_IEI_VAL, "VAL" },
+ { MYPROTO_IEI_GOO_NEST, "GOO_NEST" },
+ {}
+};
+
+const struct value_string myproto_repeat_enum_names[] = {
+ OSMO_VALUE_STRING(R_A),
+ OSMO_VALUE_STRING(R_B),
+ OSMO_VALUE_STRING(R_C),
+ {}
+};
diff --git a/tests/libosmo-gtlv/test_gtlv_gen/myproto_ies_custom.h
b/tests/libosmo-gtlv/test_gtlv_gen/myproto_ies_custom.h
new file mode 100644
index 0000000..56039af
--- /dev/null
+++ b/tests/libosmo-gtlv/test_gtlv_gen/myproto_ies_custom.h
@@ -0,0 +1,70 @@
+/* Definitions for decoded message IEs, to be used by the auto-generated
myproto_ies_auto.c. */
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <osmocom/core/utils.h>
+
+enum myproto_msg_type {
+ MYPROTO_MSGT_MOO = 1,
+ MYPROTO_MSGT_GOO = 7,
+};
+
+extern const struct value_string myproto_msg_type_names[];
+
+enum myproto_iei {
+ MYPROTO_IEI_FOO = 1,
+ MYPROTO_IEI_BAR,
+ MYPROTO_IEI_BAZ,
+ MYPROTO_IEI_REPEAT_INT,
+ MYPROTO_IEI_REPEAT_STRUCT,
+ MYPROTO_IEI_MOO_NEST,
+ MYPROTO_IEI_VAL,
+ MYPROTO_IEI_GOO_NEST,
+};
+
+extern const struct value_string myproto_iei_names[];
+
+struct myproto_ie_bar {
+ char str[23];
+};
+
+struct myproto_ie_baz {
+ int v_int;
+ bool v_bool;
+};
+
+enum myproto_repeat_enum {
+ R_A,
+ R_B,
+ R_C,
+};
+
+extern const struct value_string myproto_repeat_enum_names[];
+
+struct myproto_ie_repeat_struct {
+ int v_int;
+ bool v_bool;
+ enum myproto_repeat_enum v_enum;
+};
diff --git a/tests/libosmo-gtlv/test_tliv/Makefile.am
b/tests/libosmo-gtlv/test_tliv/Makefile.am
new file mode 100644
index 0000000..c97db3d
--- /dev/null
+++ b/tests/libosmo-gtlv/test_tliv/Makefile.am
@@ -0,0 +1,64 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ -I$(bulddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gen__myproto_ies_auto \
+ tliv_test \
+ $(NULL)
+
+EXTRA_DIST = \
+ myproto_ies_custom.h \
+ tliv_test.ok \
+ $(NULL)
+
+BUILT_SOURCES = \
+ myproto_ies_auto.h \
+ myproto_ies_auto.c \
+ $(NULL)
+
+CLEANFILES = \
+ myproto_ies_auto.h \
+ myproto_ies_auto.c \
+ $(NULL)
+
+gen__myproto_ies_auto_SOURCES = \
+ gen__myproto_ies_auto.c \
+ myproto_ies_custom.c \
+ $(NULL)
+
+gen__myproto_ies_auto_LDADD = \
+ $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.la \
+ $(TALLOC_LIBS) \
+ $(PTHREAD_LIBS) \
+ $(NULL)
+
+myproto_ies_auto.h: $(builddir)/gen__myproto_ies_auto
+ $(builddir)/gen__myproto_ies_auto h > $(builddir)/myproto_ies_auto.h
+myproto_ies_auto.c: $(builddir)/gen__myproto_ies_auto
+ $(builddir)/gen__myproto_ies_auto c > $(builddir)/myproto_ies_auto.c
+
+tliv_test_SOURCES = \
+ tliv_test.c \
+ myproto_ies_custom.c \
+ myproto_ies_auto.c \
+ $(NULL)
+
+tliv_test_LDADD = \
+ $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.la \
+ $(TALLOC_LIBS) \
+ $(PTHREAD_LIBS) \
+ $(NULL)
+
+.PHONY: update_exp
+update_exp:
+ $(builddir)/tliv_test >$(srcdir)/tliv_test.ok
diff --git a/tests/libosmo-gtlv/test_tliv/gen__myproto_ies_auto.c
b/tests/libosmo-gtlv/test_tliv/gen__myproto_ies_auto.c
new file mode 100644
index 0000000..21e70d9
--- /dev/null
+++ b/tests/libosmo-gtlv/test_tliv/gen__myproto_ies_auto.c
@@ -0,0 +1,71 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <stdbool.h>
+#include <stdio.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gtlv/gtlv_gen.h>
+
+#define O OSMO_GTLV_GEN_O
+#define M OSMO_GTLV_GEN_M
+#define O_MULTI OSMO_GTLV_GEN_O_MULTI
+#define M_MULTI OSMO_GTLV_GEN_M_MULTI
+#define O_INST OSMO_GTLV_GEN_O_INST
+#define M_INST OSMO_GTLV_GEN_M_INST
+
+#define AUTO osmo_gtlv_gen_ie_auto
+
+static const struct osmo_gtlv_gen_ie bar = {
+ .tag_name = "bar",
+};
+
+static const struct osmo_gtlv_gen_ie_o ies_in_moo_msg[] = {
+ M_INST("MYPROTO_IEI_BAR_ALPHA", bar, "bar_alpha"),
+ O_INST("MYPROTO_IEI_BAR_BETA", bar, "bar_beta"),
+ M_INST("MYPROTO_IEI_BAR_GAMMA", bar, "bar_gamma"),
+ {}
+};
+
+static const struct osmo_gtlv_gen_msg msg_defs[] = {
+ { "moo", ies_in_moo_msg },
+ {}
+};
+
+int main(int argc, const char **argv)
+{
+ struct osmo_gtlv_gen_cfg cfg = {
+ .proto_name = "myproto",
+ .message_type_enum = "enum myproto_msg_type",
+ .message_type_prefix = "MYPROTO_MSGT_",
+ .tag_enum = "enum myproto_iei",
+ .tag_prefix = "MYPROTO_IEI_",
+ .decoded_type_prefix = "struct myproto_ie_",
+ .h_header = "#include \"myproto_ies_custom.h\"",
+ .c_header = "#include <myproto_ies_auto.h>",
+ .msg_defs = msg_defs,
+ .add_enc_to_str = true,
+ };
+ return osmo_gtlv_gen_main(&cfg, argc, argv);
+}
diff --git a/tests/libosmo-gtlv/test_tliv/myproto_ies_custom.c
b/tests/libosmo-gtlv/test_tliv/myproto_ies_custom.c
new file mode 100644
index 0000000..54164ce
--- /dev/null
+++ b/tests/libosmo-gtlv/test_tliv/myproto_ies_custom.c
@@ -0,0 +1,68 @@
+/* Example for defining custom IES for gtlv_gen. */
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <inttypes.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gtlv/gtlv.h>
+
+#include <myproto_ies_custom.h>
+
+int myproto_dec_bar(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load
*gtlv)
+{
+ struct myproto_ie_bar *bar = decode_to;
+ if (gtlv->len < 2)
+ return -EINVAL;
+ *bar = (struct myproto_ie_bar){
+ .a = gtlv->val[0],
+ .b = (gtlv->val[1] == 1),
+ };
+ return 0;
+}
+
+int myproto_enc_bar(struct osmo_gtlv_put *gtlv, void *decoded_struct, void *encode_from)
+{
+ struct myproto_ie_bar *bar = encode_from;
+ msgb_put_u8(gtlv->dst, bar->a);
+ msgb_put_u8(gtlv->dst, bar->b ? 1 : 0);
+ return 0;
+}
+
+int myproto_enc_to_str_bar(char *buf, size_t buflen, void *encode_from)
+{
+ struct myproto_ie_bar *bar = encode_from;
+ return snprintf(buf, buflen, "%d,%s", bar->a, bar->b ? "true"
: "false");
+}
+
+const struct value_string myproto_msg_type_names[] = {
+ { MYPROTO_MSGT_MOO, "MOO" },
+ {}
+};
+
+const struct value_string myproto_iei_names[] = {
+ { MYPROTO_IEI_BAR, "BAR" },
+ {}
+};
diff --git a/tests/libosmo-gtlv/test_tliv/myproto_ies_custom.h
b/tests/libosmo-gtlv/test_tliv/myproto_ies_custom.h
new file mode 100644
index 0000000..64e06ff
--- /dev/null
+++ b/tests/libosmo-gtlv/test_tliv/myproto_ies_custom.h
@@ -0,0 +1,50 @@
+/* Definitions for decoded message IEs, to be used by the auto-generated
myproto_ies_auto.c. */
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <osmocom/core/utils.h>
+
+enum myproto_msg_type {
+ MYPROTO_MSGT_MOO = 1,
+};
+
+extern const struct value_string myproto_msg_type_names[];
+
+enum myproto_iei {
+ MYPROTO_IEI_BAR = 1,
+};
+
+enum myproto_iei_bar_inst {
+ MYPROTO_IEI_BAR_ALPHA = 2,
+ MYPROTO_IEI_BAR_BETA = 3,
+ MYPROTO_IEI_BAR_GAMMA = 5,
+};
+
+extern const struct value_string myproto_iei_names[];
+
+struct myproto_ie_bar {
+ int a;
+ bool b;
+};
diff --git a/tests/libosmo-gtlv/test_tliv/tliv_test.c
b/tests/libosmo-gtlv/test_tliv/tliv_test.c
new file mode 100644
index 0000000..fd4e310
--- /dev/null
+++ b/tests/libosmo-gtlv/test_tliv/tliv_test.c
@@ -0,0 +1,217 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr(a)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 <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gtlv/gtlv.h>
+
+#include <myproto_ies_auto.h>
+
+struct myproto_msg {
+ enum myproto_msg_type type;
+ union myproto_ies ies;
+};
+
+static void err_cb(void *data, void *decoded_struct, const char *file, int line, const
char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ //printf("ERR: %s:%d ", file, line);
+ printf("ERR: ");
+ vprintf(fmt, args);
+ va_end(args);
+}
+
+static int myproto_msg_enc(struct msgb *dst, const struct myproto_msg *msg, const struct
osmo_gtlv_cfg *cfg)
+{
+ struct osmo_gtlv_put gtlv = {
+ .cfg = cfg,
+ .dst = dst,
+ };
+
+ msgb_put_u8(gtlv.dst, msg->type);
+ return myproto_ies_encode(>lv, (void *)&msg->ies, msg->type, err_cb,
NULL, myproto_iei_names);
+}
+
+static int myproto_msg_dec(struct myproto_msg *msg, const uint8_t *data, size_t
data_len,
+ const struct osmo_gtlv_cfg *cfg, bool ordered)
+{
+ struct osmo_gtlv_load gtlv;
+ if (data_len < 1)
+ return -EINVAL;
+ msg->type = data[0];
+ gtlv = (struct osmo_gtlv_load){
+ .cfg = cfg,
+ .src = { data + 1, data_len - 1 },
+ };
+ return myproto_ies_decode(&msg->ies, >lv, ordered, msg->type, err_cb,
NULL, myproto_iei_names);
+}
+
+void *ctx;
+
+struct myproto_msg tests[] = {
+ {
+ MYPROTO_MSGT_MOO,
+ {
+ .moo = {
+ .bar_alpha = { 23, true },
+ .bar_gamma = { 42, false },
+ },
+ },
+ },
+ {
+ MYPROTO_MSGT_MOO,
+ {
+ .moo = {
+ .bar_alpha = { 11, true },
+ .bar_beta_present = true,
+ .bar_beta = { 22, false },
+ .bar_gamma = { 33, true },
+ },
+ },
+ },
+};
+
+int myproto_msg_to_str_buf(char *buf, size_t buflen, const struct myproto_msg *m)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "%s={", get_value_string(myproto_msg_type_names,
m->type));
+ OSMO_STRBUF_APPEND(sb, osmo_gtlvs_encode_to_str_buf, &m->ies, 0,
myproto_get_msg_coding(m->type),
+ myproto_iei_names);
+ OSMO_STRBUF_PRINTF(sb, " }");
+ return sb.chars_needed;
+
+}
+
+char *myproto_msg_to_str(const struct myproto_msg *m)
+{
+ OSMO_NAME_C_IMPL(ctx, 256, "ERROR", myproto_msg_to_str_buf, m)
+}
+
+void test_enc_dec(const char *label, const struct osmo_gtlv_cfg *cfg, bool ordered)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ int rc;
+ const struct myproto_msg *orig = &tests[i];
+ struct myproto_msg parsed = {};
+ struct msgb *msg;
+
+ printf("\n=== start %s %s[%d]\n", label, __func__, i);
+ printf("encoded: %s\n", myproto_msg_to_str(orig));
+
+ msg = msgb_alloc(1024, __func__);
+ rc = myproto_msg_enc(msg, orig, cfg);
+ printf("myproto_msg_enc() rc = %d\n", rc);
+ printf("%s.\n", osmo_hexdump(msg->data, msg->len));
+
+ rc = myproto_msg_dec(&parsed, msg->data, msg->len, cfg, ordered);
+ printf("myproto_msg_dec() rc = %d\n", rc);
+ printf("decoded: %s\n", myproto_msg_to_str(&parsed));
+ if (strcmp(myproto_msg_to_str(orig), myproto_msg_to_str(&parsed))) {
+ printf(" ERROR: parsed != orig\n");
+ exit(1);
+ }
+
+ msgb_free(msg);
+ printf("=== end %s %s[%d]\n", label, __func__, i);
+ }
+}
+
+/* Example of defining a TLI, with an instance indicator */
+static int tliv_load_tl(struct osmo_gtlv_load *gtlv, const uint8_t *src_data, size_t
src_data_len)
+{
+ /* already validated in next_tl_valid(): src_data_len >= cfg->tl_min_size == 2.
*/
+ gtlv->ti.tag = src_data[0];
+ gtlv->len = src_data[1];
+
+ switch (gtlv->ti.tag) {
+ /* All tags that are TLIV go here */
+ case MYPROTO_IEI_BAR:
+ if (src_data_len < 3)
+ return -ENOSPC;
+ gtlv->ti.instance_present = true;
+ gtlv->ti.instance = src_data[2];
+ gtlv->val = src_data + 3;
+ /* In this example, the I is part of the len */
+ gtlv->len--;
+ return 0;
+ default:
+ gtlv->val = src_data + 2;
+ return 0;
+ }
+}
+
+static int tliv_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct
osmo_gtlv_tag_inst *ti, size_t len,
+ struct osmo_gtlv_put *gtlv)
+{
+ if (ti->tag > UINT8_MAX)
+ return -EINVAL;
+ if (len > UINT8_MAX)
+ return -EMSGSIZE;
+ if (dst_data_avail < 2)
+ return -ENOSPC;
+
+ dst_data[0] = ti->tag;
+
+ switch (ti->tag) {
+ /* All tags that are TLIV go here */
+ case MYPROTO_IEI_BAR:
+ if (dst_data_avail < 3)
+ return -ENOSPC;
+ if (!ti->instance_present)
+ return -EINVAL;
+ if (ti->instance > UINT8_MAX)
+ return -EINVAL;
+ /* here, I is part of the len in L; the passed len reflects only the value, so add 1
for I */
+ dst_data[1] = len + 1;
+ dst_data[2] = ti->instance;
+ return 3;
+ default:
+ dst_data[1] = len;
+ return 2;
+ }
+}
+
+const struct osmo_gtlv_cfg osmo_tliv_cfg = {
+ .tl_min_size = 2,
+ .load_tl = tliv_load_tl,
+ .store_tl = tliv_store_tl,
+};
+
+int main()
+{
+ ctx = talloc_named_const(NULL, 0, "test_gen_tlv");
+ msgb_talloc_ctx_init(ctx, 0);
+
+ test_enc_dec("tliv ordered", &osmo_tliv_cfg, true);
+ test_enc_dec("tliv unordered", &osmo_tliv_cfg, false);
+
+ talloc_free(ctx);
+ return 0;
+}
diff --git a/tests/libosmo-gtlv/test_tliv/tliv_test.ok
b/tests/libosmo-gtlv/test_tliv/tliv_test.ok
new file mode 100644
index 0000000..184ffa6
--- /dev/null
+++ b/tests/libosmo-gtlv/test_tliv/tliv_test.ok
@@ -0,0 +1,32 @@
+
+=== start tliv ordered test_enc_dec[0]
+encoded: MOO={ 'BAR'=23,true 'BAR'=42,false }
+myproto_msg_enc() rc = 0
+01 01 03 02 17 01 01 03 05 2a 00 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'BAR'=23,true 'BAR'=42,false }
+=== end tliv ordered test_enc_dec[0]
+
+=== start tliv ordered test_enc_dec[1]
+encoded: MOO={ 'BAR'=11,true 'BAR'=22,false 'BAR'=33,true }
+myproto_msg_enc() rc = 0
+01 01 03 02 0b 01 01 03 03 16 00 01 03 05 21 01 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'BAR'=11,true 'BAR'=22,false 'BAR'=33,true }
+=== end tliv ordered test_enc_dec[1]
+
+=== start tliv unordered test_enc_dec[0]
+encoded: MOO={ 'BAR'=23,true 'BAR'=42,false }
+myproto_msg_enc() rc = 0
+01 01 03 02 17 01 01 03 05 2a 00 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'BAR'=23,true 'BAR'=42,false }
+=== end tliv unordered test_enc_dec[0]
+
+=== start tliv unordered test_enc_dec[1]
+encoded: MOO={ 'BAR'=11,true 'BAR'=22,false 'BAR'=33,true }
+myproto_msg_enc() rc = 0
+01 01 03 02 0b 01 01 03 03 16 00 01 03 05 21 01 .
+myproto_msg_dec() rc = 0
+decoded: MOO={ 'BAR'=11,true 'BAR'=22,false 'BAR'=33,true }
+=== end tliv unordered test_enc_dec[1]
diff --git a/tests/testsuite.at b/tests/testsuite.at
index ca133bb..2052198 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -298,6 +298,30 @@
AT_CHECK([$abs_top_builddir/tests/tlv/tlv_test], [0], [expout], [ignore])
AT_CLEANUP
+AT_SETUP([gtlv])
+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
+
+AT_SETUP([gtlv_gen])
+AT_KEYWORDS([gtlv_gen])
+cat $abs_srcdir/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/libosmo-gtlv/test_gtlv_gen/gtlv_gen_test], [],
[expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([tliv])
+AT_KEYWORDS([tliv])
+cat $abs_srcdir/libosmo-gtlv/test_tliv/tliv_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/libosmo-gtlv/test_tliv/tliv_test], [], [expout],
[ignore])
+AT_CLEANUP
+
AT_SETUP([gsup])
AT_KEYWORDS([gsup])
cat $abs_srcdir/gsup/gsup_test.ok > expout
--
To view, visit
https://gerrit.osmocom.org/c/libosmocore/+/28286
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings
Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Change-Id: I25ab400f0c5707fdc0d8e480aca19871c2e26e71
Gerrit-Change-Number: 28286
Gerrit-PatchSet: 1
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>
Gerrit-MessageType: newchange