neels has uploaded this change for review.
libosmo-tlv: add C code generator for IE structs and arrays
Defining a protocol of message types with lists of IEs bears a lot of
repetitive, copy-paste-error-prone writing out of data structures.
Add a third layer to libosmo-tlv, which allows helpful code generation.
By non-repetitive data structures that briefly describe the protocol's
messages and IEs, generate possibly repetitive IE list arrays and
decoded-struct definitions automatically, avoiding grunt work errors.
I tried C macros for this at first, but it became too convoluted.
Generating C code that can be read and grepped makes things easier.
A usage example is found in tests/libosmo-tlv/test_tlv_gen/.
Related: SYS#5599
Change-Id: Ifb3ea54d2797ce060b95834aa117725ec2d6c4cf
---
M configure.ac
M include/osmocom/tlv/Makefile.am
A include/osmocom/tlv/tlv_gen.h
M src/libosmo-tlv/Makefile.am
A src/libosmo-tlv/tlv_gen.c
M tests/libosmo-tlv/Makefile.am
A tests/libosmo-tlv/test_tlv_gen/Makefile.am
A tests/libosmo-tlv/test_tlv_gen/gen__myproto_ies_auto.c
A tests/libosmo-tlv/test_tlv_gen/myproto_ies_custom.c
A tests/libosmo-tlv/test_tlv_gen/myproto_ies_custom.h
A tests/libosmo-tlv/test_tlv_gen/tlv_gen_test.c
A tests/libosmo-tlv/test_tlv_gen/tlv_gen_test.ok
M tests/testsuite.at
13 files changed, 1,324 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-upf refs/changes/19/27219/1
diff --git a/configure.ac b/configure.ac
index 70879e9..f9e85ce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -205,6 +205,7 @@
tests/Makefile
tests/atlocal
tests/libosmo-tlv/Makefile
+ tests/libosmo-tlv/test_tlv_gen/Makefile
doc/Makefile
doc/examples/Makefile
doc/manuals/Makefile
diff --git a/include/osmocom/tlv/Makefile.am b/include/osmocom/tlv/Makefile.am
index 9843369..979bd13 100644
--- a/include/osmocom/tlv/Makefile.am
+++ b/include/osmocom/tlv/Makefile.am
@@ -1,6 +1,7 @@
tlv_HEADERS = \
tlv.h \
tlv_dec_enc.h \
+ tlv_gen.h \
$(NULL)
tlvdir = $(includedir)/osmocom/tlv
diff --git a/include/osmocom/tlv/tlv_gen.h b/include/osmocom/tlv/tlv_gen.h
new file mode 100644
index 0000000..9537371
--- /dev/null
+++ b/include/osmocom/tlv/tlv_gen.h
@@ -0,0 +1,111 @@
+/* Write h and c source files for TLV protocol definitions, based on very sparse TLV definitions.
+ * For a usage example see tests/libosmo-tlv/test_tlv_gen/. */
+#pragma once
+
+#include <stdbool.h>
+
+struct osmo_tlv_gen_ie;
+
+/*! Modifier for Mandatory/Optional/Multiple around an osmo_tlv_gen_ie. */
+struct osmo_tlv_gen_ie_o {
+ /*! Whether to add a bool foo_present, and to skip the coding if false. */
+ 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. */
+ unsigned int multi;
+ /*! IE decoding / encoding instructions */
+ const struct osmo_tlv_gen_ie *ie;
+};
+
+/*! Define decoding and encoding of a single IE, i.e. one full TLV. */
+struct osmo_tlv_gen_ie {
+ /*! Member name.
+ * The C name of the member in a decoded struct.
+ * Some other names, if NULL, are derived from this name.
+ *
+ * For example, simply this
+ *
+ * struct osmo_tlv_gen_ie bar {
+ * .name = "bar",
+ * };
+ *
+ * Generates an osmo_tlv_coding entry of
+ *
+ * { MYPROTO_IEI_BAR,
+ * .memb_ofs = offsetof(struct myproto_foo, bar),
+ * .dec_func = myproto_dec_bar,
+ * .enc_func = myproto_enc_bar,
+ * }
+ */
+ const char *name;
+
+ /*! like "uint32_t".
+ * If NULL, auto-generate a "struct myproto_ie_foo" instead, useful only for an IE that contains nested IEs
+ * where the struct members can be derived from the nested_ies list.
+ */
+ const char *decoded_type;
+
+ /*! C name of this tag value, e.g. "MYPROTO_IEI_FOO". If NULL, take "MYPROTO_IEI_"+upper(name) instead. */
+ const char *tag_name;
+
+ /*! Name of the dec/enc functions. "foo" -> myproto_dec_foo(), myproto_enc_foo().
+ * These functions need to be implemented by the caller in a myproto_ies_custom.c. */
+ const char *dec_enc;
+
+ /*! Name of the enc_to_str function. "foo" -> myproto_enc_to_str_foo(). optional. */
+ const char *to_str;
+
+ /*! List of inner IEs terminated by {0}. If non-NULL, this is a "Grouped IE" with an inner TLV structure inside
+ * this IE's V part. */
+ const struct osmo_tlv_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_tlv_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_tlv_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_tlv_gen_msg *msg_defs;
+};
+
+/*! 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_tlv_gen_msg {
+ const char *name;
+ const struct osmo_tlv_gen_ie_o *ies;
+};
+
+int osmo_tlv_gen_main(const struct osmo_tlv_gen_cfg *cfg, int argc, const char **argv);
diff --git a/src/libosmo-tlv/Makefile.am b/src/libosmo-tlv/Makefile.am
index 2b8a374..6c3b3d9 100644
--- a/src/libosmo-tlv/Makefile.am
+++ b/src/libosmo-tlv/Makefile.am
@@ -23,4 +23,5 @@
libosmo_tlv_a_SOURCES = \
tlv.c \
tlv_dec_enc.c \
+ tlv_gen.c \
$(NULL)
diff --git a/src/libosmo-tlv/tlv_gen.c b/src/libosmo-tlv/tlv_gen.c
new file mode 100644
index 0000000..17c5588
--- /dev/null
+++ b/src/libosmo-tlv/tlv_gen.c
@@ -0,0 +1,385 @@
+/* Write h and c source files for TLV protocol definitions, based on very sparse TLV definitions.
+ * For a usage example see tests/libosmo-tlv/test_tlv_gen/.
+ *
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/tlv/tlv_gen.h>
+
+static const struct osmo_tlv_gen_cfg *g_cfg = NULL;
+
+/* Helps avoid redundant defintions of the same type. */
+struct seen_entry {
+ struct llist_head entry;
+ char str[256];
+};
+static LLIST_HEAD(seen_list);
+
+static bool seen(const char *str)
+{
+ struct seen_entry *s;
+ llist_for_each_entry(s, &seen_list, entry) {
+ if (!strcmp(s->str, str))
+ return true;
+ }
+ s = talloc_zero(NULL, struct seen_entry);
+ OSMO_STRLCPY_ARRAY(s->str, str);
+ 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_tlv_gen_ie *ie)
+{
+ static char b[255];
+ if (ie->decoded_type)
+ return ie->decoded_type;
+ /* "struct foo_ie_" + "bar" = struct foo_ie_bar*/
+ snprintf(b, sizeof(b), "%s%s", g_cfg->decoded_type_prefix, ie->tag_name ? : ie->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_tlv_gen_ie_o ies[])
+{
+ for (; ies->ie; ies++) {
+ const struct osmo_tlv_gen_ie *ie = ies->ie;
+ if (ies->optional)
+ printf("\tbool %s_present;\n", ie->name);
+ printf("\t%s %s", decoded_type(ie), ie->name);
+ if (ies->multi) {
+ printf("[%u];\n", ies->multi);
+ printf("\tunsigned int %s_count", ie->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_tlv_gen_ie_o ies[])
+{
+ if (!ies)
+ return;
+ for (; ies->ie; ies++) {
+ const struct osmo_tlv_gen_ie *ie = ies->ie;
+ if (!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)))
+ 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));
+ 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_tlv_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_tlv_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/tlv/tlv_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_tlv_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_tlv_load *tlv, bool tlv_ordered,\n"
+ " %s message_type, osmo_tlv_err_cb err_cb, 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_tlv_put *tlv, union %s_ies *src,\n"
+ " %s message_type, osmo_tlv_err_cb err_cb, 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_tlv_gen_ie_o *ies)
+{
+ for (; ies->ie; ies++) {
+ const struct osmo_tlv_gen_ie *ie = ies->ie;
+ const char *dec_enc = ie->dec_enc ? : (ie->tag_name ? : ie->name);
+ if (ie->nested_ies) {
+ write_extern_dec_enc(ie->nested_ies);
+ continue;
+ }
+ if (seen(dec_enc))
+ continue;
+ printf("extern int %s_dec_%s(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv);\n",
+ g_cfg->proto_name, dec_enc);
+ printf("extern int %s_enc_%s(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from);\n",
+ g_cfg->proto_name, dec_enc);
+ if (ie->to_str)
+ printf("extern int %s_enc_to_str_%s(char *buf, size_t buflen, void *encode_from);\n",
+ g_cfg->proto_name, ie->to_str);
+ }
+}
+
+/* For a nested IE, write the struct osmo_tlv_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_tlv_gen_ie_o *ies, const char *obj_type, const char *substruct)
+{
+#define printi(FMT, ARGS...) printf("%s" FMT, indent, ##ARGS)
+
+ for (; ies->ie; ies++) {
+ const struct osmo_tlv_gen_ie *ie = ies->ie;
+ const char *tag_name = ie->tag_name ? : ie->name;
+ printi("{ %s%s,\n", g_cfg->tag_prefix, osmo_str_toupper(tag_name));
+ printi(" .memb_ofs = offsetof(%s, %s%s),\n", obj_type, substruct, ie->name);
+ if (ie->nested_ies) {
+ printi(" .nested_ies = ies_in_%s,\n", ie->name);
+ } else {
+ const char *dec_enc = ie->dec_enc ? : (ie->tag_name ? : ie->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 (ie->to_str)
+ printi(" .enc_to_str_func = %s_enc_to_str_%s,\n", g_cfg->proto_name, ie->to_str);
+ }
+ if (ies->multi) {
+ printi(" .memb_array_pitch = OSMO_MEMB_ARRAY_PITCH(%s, %s%s),\n",
+ obj_type, substruct, ie->name);
+ printi(" .has_count = true, .max_count = %u,\n", ies->multi);
+ printi(" .count_ofs = offsetof(%s, %s%s_count),\n", obj_type, substruct, ie->name);
+ }
+ if (ies->optional) {
+ printi(" .has_presence_flag = true,\n");
+ printi(" .presence_flag_ofs = offsetof(%s, %s%s_present),\n", obj_type, substruct, ie->name);
+ }
+ printi("},\n");
+ }
+}
+
+/* For a nested IE, write the struct osmo_tlv_coding array of the inner IEs.
+ * static const struct osmo_tlv_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_tlv_gen_ie_o *ies)
+{
+ const char *indent = "\t";
+ for (; ies->ie; ies++) {
+ const struct osmo_tlv_gen_ie *ie = ies->ie;
+ if (!ie->nested_ies)
+ continue;
+ write_nested_ies_array(ie->nested_ies);
+
+ if (seen(ie->name))
+ continue;
+
+ printf("\nstatic const struct osmo_tlv_coding ies_in_%s[] = {\n", ie->name);
+ write_ies_array(indent, ie->nested_ies, decoded_type(ie), "");
+ printi("{0}\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_tlv_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/tlv/tlv.h>\n");
+ printf("#include <osmocom/tlv/tlv_dec_enc.h>\n");
+ printf("#include <osmocom/tlv/tlv_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_tlv_coding ies_in_msg_%s[] = {\n", gen_msg->name);
+ write_ies_array("\t", gen_msg->ies, obj_type, substruct);
+ printf("\t{0}\n};\n");
+ talloc_free(substruct);
+ talloc_free(obj_type);
+ }
+ printf("\nstatic const struct osmo_tlv_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_tlv_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_tlv_load *tlv, bool tlv_ordered,\n"
+ " %s message_type,\n"
+ " osmo_tlv_err_cb err_cb, const struct value_string *iei_strs)\n"
+ "{\n"
+ " return osmo_tlvs_decode(dst, 0, tlv, tlv_ordered, %s_get_msg_coding(message_type), err_cb, 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_tlv_put *tlv, union %s_ies *src,\n"
+ " %s message_type, osmo_tlv_err_cb err_cb, const struct value_string *iei_strs)\n"
+ "{\n"
+ " return osmo_tlvs_encode(tlv, src, 0, %s_get_msg_coding(message_type), err_cb, 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_tlv_gen_main(const struct osmo_tlv_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/libosmo-tlv/Makefile.am b/tests/libosmo-tlv/Makefile.am
index ff3fba9..64e3bdd 100644
--- a/tests/libosmo-tlv/Makefile.am
+++ b/tests/libosmo-tlv/Makefile.am
@@ -1,3 +1,7 @@
+SUBDIRS = \
+ test_tlv_gen \
+ $(NULL)
+
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
@@ -40,3 +44,4 @@
update_exp:
$(builddir)/tlv_test >$(srcdir)/tlv_test.ok
$(builddir)/tlv_dec_enc_test >$(srcdir)/tlv_dec_enc_test.ok
+ $(MAKE) -C test_tlv_gen update_exp
diff --git a/tests/libosmo-tlv/test_tlv_gen/Makefile.am b/tests/libosmo-tlv/test_tlv_gen/Makefile.am
new file mode 100644
index 0000000..79c925c
--- /dev/null
+++ b/tests/libosmo-tlv/test_tlv_gen/Makefile.am
@@ -0,0 +1,60 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(bulddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gen__myproto_ies_auto \
+ tlv_gen_test \
+ $(NULL)
+
+EXTRA_DIST = \
+ myproto_ies_custom.h \
+ tlv_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/libosmo-tlv/libosmo-tlv.a \
+ $(LIBOSMOCORE_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
+
+tlv_gen_test_SOURCES = \
+ tlv_gen_test.c \
+ myproto_ies_custom.c \
+ myproto_ies_auto.c \
+ $(NULL)
+
+tlv_gen_test_LDADD = \
+ $(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \
+ $(LIBOSMOCORE_LIBS) \
+ $(NULL)
+
+.PHONY: update_exp
+update_exp:
+ $(builddir)/tlv_gen_test >$(srcdir)/tlv_gen_test.ok
diff --git a/tests/libosmo-tlv/test_tlv_gen/gen__myproto_ies_auto.c b/tests/libosmo-tlv/test_tlv_gen/gen__myproto_ies_auto.c
new file mode 100644
index 0000000..1d5ea4a
--- /dev/null
+++ b/tests/libosmo-tlv/test_tlv_gen/gen__myproto_ies_auto.c
@@ -0,0 +1,135 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/tlv/tlv_gen.h>
+
+#define O(IE_INST) { .optional = true, .ie = &IE_INST }
+#define M(IE_INST) { .ie = &(IE_INST) }
+#define MULTI(MAX, IE_INST) { .multi = MAX, .ie = &(IE_INST) }
+
+static const struct osmo_tlv_gen_ie foo = {
+ .name = "foo",
+ .decoded_type = "int", /* adds 'int foo;' to the struct */
+ .dec_enc = "u16", /* uses myproto_dec_u16() and myproto_enc_u16() for the TLV value part */
+ .to_str = "u16", /* uses myproto_enc_to_str_u16() */
+ .spec_ref = "an int coded as uint16_t",
+};
+
+static const struct osmo_tlv_gen_ie bar = {
+ .name = "bar",
+ /* uses 'struct ie_bar bar;' and dec_bar()/enc_bar() */
+ .to_str = "bar", /* uses myproto_enc_to_str_bar() */
+};
+
+static const struct osmo_tlv_gen_ie baz = {
+ .name = "baz",
+ /* uses 'struct ie_baz baz;' and dec_baz()/enc_baz() */
+ .to_str = "baz",
+};
+
+static const struct osmo_tlv_gen_ie repeat_int = {
+ .name = "repeat_int",
+ .decoded_type = "int", /* adds 'int repeat_int;' to the struct */
+ .dec_enc = "u16", /* uses dec_u16() and enc_u16() for the TLV value part */
+ .to_str = "u16",
+};
+
+static const struct osmo_tlv_gen_ie repeat_struct = {
+ .name = "repeat_struct",
+ .to_str = "repeat_struct",
+};
+
+static const struct osmo_tlv_gen_ie_o ies_in_moo_nest[] = {
+ M(foo),
+ M(bar),
+ M(baz),
+ {0}
+};
+
+static const struct osmo_tlv_gen_ie moo_nest = {
+ .name = "moo_nest",
+ .nested_ies = ies_in_moo_nest,
+};
+
+static const struct osmo_tlv_gen_ie val = {
+ .name = "val",
+ .decoded_type = "uint64_t",
+ .dec_enc = "u64",
+ .to_str = "u64",
+};
+
+static const struct osmo_tlv_gen_ie_o ies_in_goo_nest[] = {
+ O(val),
+ M(moo_nest),
+ {0}
+};
+
+static const struct osmo_tlv_gen_ie goo_nest = {
+ .name = "goo_nest",
+ .nested_ies = ies_in_goo_nest,
+};
+
+static const struct osmo_tlv_gen_ie_o ies_in_moo_msg[] = {
+ M(foo),
+ M(bar),
+ O(baz),
+ MULTI(32, repeat_int),
+ MULTI(32, repeat_struct),
+ O(moo_nest),
+ {0}
+};
+
+static const struct osmo_tlv_gen_ie_o ies_in_goo_msg[] = {
+ M(foo),
+ O(bar),
+ MULTI(8, goo_nest),
+ {0}
+};
+
+static const struct osmo_tlv_gen_msg msg_defs[] = {
+ { "moo", ies_in_moo_msg },
+ { "goo", ies_in_goo_msg },
+ {0}
+};
+
+int main(int argc, const char **argv)
+{
+ struct osmo_tlv_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,
+ };
+ return osmo_tlv_gen_main(&cfg, argc, argv);
+}
diff --git a/tests/libosmo-tlv/test_tlv_gen/myproto_ies_custom.c b/tests/libosmo-tlv/test_tlv_gen/myproto_ies_custom.c
new file mode 100644
index 0000000..c45eae5
--- /dev/null
+++ b/tests/libosmo-tlv/test_tlv_gen/myproto_ies_custom.c
@@ -0,0 +1,156 @@
+#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/tlv/tlv.h>
+
+#include <myproto_ies_custom.h>
+
+int myproto_dec_u16(void *decoded_struct, void *decode_to, const struct osmo_tlv_load *tlv)
+{
+ int *foo = decode_to;
+ if (tlv->len != 2)
+ return -EINVAL;
+ *foo = osmo_load16be(tlv->val);
+ return 0;
+}
+
+int myproto_enc_u16(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
+{
+ int *foo = encode_from;
+ if (*foo > INT16_MAX)
+ return -EINVAL;
+ msgb_put_u16(tlv->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_tlv_load *tlv)
+{
+ uint64_t *val = decode_to;
+ if (tlv->len != sizeof(uint64_t))
+ return -EINVAL;
+ *val = osmo_load64be(tlv->val);
+ return 0;
+}
+
+int myproto_enc_u64(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
+{
+ uint64_t *val = encode_from;
+ osmo_store64be(*val, msgb_put(tlv->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_tlv_load *tlv)
+{
+ struct myproto_ie_bar *bar = decode_to;
+ if (tlv->len > sizeof(bar->str) - 1)
+ return -EINVAL;
+ osmo_strlcpy(bar->str, (const char*)tlv->val, OSMO_MIN(tlv->len + 1, sizeof(bar->str)));
+ return 0;
+}
+
+int myproto_enc_bar(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
+{
+ struct myproto_ie_bar *bar = encode_from;
+ int len = strnlen(bar->str, sizeof(bar->str));
+ memcpy(msgb_put(tlv->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_tlv_load *tlv)
+{
+ struct myproto_ie_baz *baz = decode_to;
+ uint16_t l;
+ if (tlv->len != 2)
+ return -EINVAL;
+ l = osmo_load16be(tlv->val);
+ baz->v_int = l & 0x7fff;
+ baz->v_bool = (l & 0x8000) ? true : false;
+ return 0;
+}
+
+int myproto_enc_baz(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
+{
+ struct myproto_ie_baz *baz = encode_from;
+ if (baz->v_int > 0x7fff)
+ return -EINVAL;
+ msgb_put_u16(tlv->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_tlv_load *tlv)
+{
+ struct myproto_ie_repeat_struct *repeat_struct = decode_to;
+ if (tlv->len != 3)
+ return -EINVAL;
+ repeat_struct->v_int = osmo_load16be(tlv->val);
+ repeat_struct->v_bool = tlv->val[2] & 0x80;
+ repeat_struct->v_enum = tlv->val[2] & 0x7f;
+ return 0;
+}
+
+int myproto_enc_repeat_struct(struct osmo_tlv_put *tlv, void *decoded_struct, void *encode_from)
+{
+ struct myproto_ie_repeat_struct *repeat_struct = encode_from;
+ msgb_put_u16(tlv->dst, repeat_struct->v_int);
+ msgb_put_u8(tlv->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" },
+ {0}
+};
+
+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" },
+ {0}
+};
+
+const struct value_string myproto_repeat_enum_names[] = {
+ OSMO_VALUE_STRING(R_A),
+ OSMO_VALUE_STRING(R_B),
+ OSMO_VALUE_STRING(R_C),
+ {0}
+};
diff --git a/tests/libosmo-tlv/test_tlv_gen/myproto_ies_custom.h b/tests/libosmo-tlv/test_tlv_gen/myproto_ies_custom.h
new file mode 100644
index 0000000..bf65f4d
--- /dev/null
+++ b/tests/libosmo-tlv/test_tlv_gen/myproto_ies_custom.h
@@ -0,0 +1,47 @@
+/* Definitions for decoded message IEs, to be used by the auto-generated tlv_gen_test_tlv.c. */
+#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-tlv/test_tlv_gen/tlv_gen_test.c b/tests/libosmo-tlv/test_tlv_gen/tlv_gen_test.c
new file mode 100644
index 0000000..4135f50
--- /dev/null
+++ b/tests/libosmo-tlv/test_tlv_gen/tlv_gen_test.c
@@ -0,0 +1,256 @@
+/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero 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/tlv/tlv.h>
+
+#include <myproto_ies_auto.h>
+
+struct myproto_msg {
+ enum myproto_msg_type type;
+ union myproto_ies ies;
+};
+
+static void err_cb(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_tlv_cfg *cfg)
+{
+ struct osmo_tlv_put tlv = {
+ .cfg = cfg,
+ .dst = dst,
+ };
+
+ msgb_put_u8(tlv.dst, msg->type);
+ return myproto_ies_encode(&tlv, (void*)&msg->ies, msg->type, err_cb, myproto_iei_names);
+}
+
+static int myproto_msg_dec(struct myproto_msg *msg, const uint8_t *data, size_t data_len,
+ const struct osmo_tlv_cfg *cfg, bool ordered)
+{
+ struct osmo_tlv_load tlv;
+ if (data_len < 1)
+ return -EINVAL;
+ msg->type = data[0];
+ tlv = (struct osmo_tlv_load){
+ .cfg = cfg,
+ .src = { data + 1, data_len - 1 },
+ };
+ return myproto_ies_decode(&msg->ies, &tlv, ordered, msg->type, err_cb, 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,
+ },
+ },
+
+ .moo_nest_present = true,
+ .moo_nest = {
+ .foo = 42,
+ .bar = { "fortytwo" },
+ .baz = {
+ .v_int = 4242,
+ .v_bool = false,
+ },
+ },
+ },
+ },
+ },
+ {
+ MYPROTO_MSGT_GOO,
+ {
+ .goo = {
+ .foo = 17,
+
+ .bar_present = true,
+ .bar = { "gooei" },
+
+ .goo_nest_count = 2,
+ .goo_nest = {
+ {
+ .val_present = true,
+ .val = 0x0123456789abcdef,
+ .moo_nest = {
+ .foo = 11,
+ .bar = { "eleven" },
+ .baz = {
+ .v_int = 1111,
+ .v_bool = true,
+ },
+ },
+ },
+ {
+ .val_present = false,
+ .moo_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_tlvs_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_tlv_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 = {0};
+ 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-tlv/test_tlv_gen/tlv_gen_test.ok b/tests/libosmo-tlv/test_tlv_gen/tlv_gen_test.ok
new file mode 100644
index 0000000..9974d67
--- /dev/null
+++ b/tests/libosmo-tlv/test_tlv_gen/tlv_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/testsuite.at b/tests/testsuite.at
index 3f7fcee..2df6e19 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -12,3 +12,9 @@
cat $abs_srcdir/libosmo-tlv/tlv_dec_enc_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/libosmo-tlv/tlv_dec_enc_test], [], [expout], [ignore])
AT_CLEANUP
+
+AT_SETUP([tlv_gen])
+AT_KEYWORDS([tlv_gen])
+cat $abs_srcdir/libosmo-tlv/test_tlv_gen/tlv_gen_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/libosmo-tlv/test_tlv_gen/tlv_gen_test], [], [expout], [ignore])
+AT_CLEANUP
To view, visit change 27219. To unsubscribe, or for help writing mail filters, visit settings.