aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <nhofmeyr@sysmocom.de>2022-01-12 02:38:39 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2022-06-16 13:04:33 +0200
commit2100097ef1be98a338b583f15bb5f529690829ff (patch)
treead4d0d13806e476472581b2bde554c6595cd9848
parentcf11908f9c39ee98b063d1ca33770179f34a3652 (diff)
libosmo-gtlv: add generic TLV de- and encoder
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(). Related: SYS#5599 Change-Id: Ib0fd00d9f288ffe13b7e67701f3e47073587404a
-rw-r--r--configure.ac3
-rw-r--r--include/osmocom/Makefile.am1
-rw-r--r--include/osmocom/gtlv/Makefile.am5
-rw-r--r--include/osmocom/gtlv/gtlv.h137
-rw-r--r--src/Makefile.am1
-rw-r--r--src/libosmo-gtlv/Makefile.am25
-rw-r--r--src/libosmo-gtlv/gtlv.c280
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/libosmo-gtlv/Makefile.am30
-rw-r--r--tests/libosmo-gtlv/gtlv_test.c513
-rw-r--r--tests/libosmo-gtlv/gtlv_test.ok256
-rw-r--r--tests/testsuite.at6
12 files changed, 1258 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index 7cf4362..96e6856 100644
--- a/configure.ac
+++ b/configure.ac
@@ -87,11 +87,14 @@ AM_CONFIG_HEADER(config.h)
AC_OUTPUT(
include/Makefile
include/osmocom/Makefile
+ include/osmocom/gtlv/Makefile
include/osmocom/pfcp/Makefile
src/Makefile
+ src/libosmo-gtlv/Makefile
src/libosmo-pfcp/Makefile
tests/Makefile
tests/atlocal
+ tests/libosmo-gtlv/Makefile
doc/Makefile
contrib/Makefile
Makefile)
diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am
index 0800830..79ca2df 100644
--- a/include/osmocom/Makefile.am
+++ b/include/osmocom/Makefile.am
@@ -1,3 +1,4 @@
SUBDIRS = \
+ gtlv \
pfcp \
$(NULL)
diff --git a/include/osmocom/gtlv/Makefile.am b/include/osmocom/gtlv/Makefile.am
new file mode 100644
index 0000000..f922ab8
--- /dev/null
+++ b/include/osmocom/gtlv/Makefile.am
@@ -0,0 +1,5 @@
+tlv_HEADERS = \
+ gtlv.h \
+ $(NULL)
+
+tlvdir = $(includedir)/osmocom/gtlv
diff --git a/include/osmocom/gtlv/gtlv.h b/include/osmocom/gtlv/gtlv.h
new file mode 100644
index 0000000..7acf558
--- /dev/null
+++ b/include/osmocom/gtlv/gtlv.h
@@ -0,0 +1,137 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdio.h>
+
+struct msgb;
+struct osmo_gtlv_load;
+struct osmo_gtlv_put;
+
+/*! 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 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 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, unsigned int tag, 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. */
+ unsigned int tag;
+ /*! 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);
+int osmo_gtlv_load_next_by_tag(struct osmo_gtlv_load *gtlv, unsigned int tag);
+
+/* 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 */
+ unsigned int last_tag;
+ 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_update_tl(struct osmo_gtlv_put *gtlv);
diff --git a/src/Makefile.am b/src/Makefile.am
index c1ce504..40a6ac1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,4 @@
SUBDIRS = \
+ libosmo-gtlv \
libosmo-pfcp \
$(NULL)
diff --git a/src/libosmo-gtlv/Makefile.am b/src/libosmo-gtlv/Makefile.am
new file mode 100644
index 0000000..7ebfb60
--- /dev/null
+++ b/src/libosmo-gtlv/Makefile.am
@@ -0,0 +1,25 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ -I$(builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(LIBOSMOCORE_LIBS) \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+noinst_LIBRARIES = \
+ libosmo-gtlv.a \
+ $(NULL)
+
+libosmo_gtlv_a_SOURCES = \
+ gtlv.c \
+ $(NULL)
diff --git a/src/libosmo-gtlv/gtlv.c b/src/libosmo-gtlv/gtlv.c
new file mode 100644
index 0000000..997a67a
--- /dev/null
+++ b/src/libosmo-gtlv/gtlv.c
@@ -0,0 +1,280 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gtlv/gtlv.h>
+
+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(&gtlv)) {
+ * 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);
+ 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.
+ * \returns the tag number 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)
+{
+ const uint8_t *ie_start;
+ size_t buflen_left;
+ int rc;
+ /* Guard against modification by load_tl(). */
+ struct osmo_gtlv_load mtlv = *gtlv;
+
+ 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;
+ return mtlv.tag;
+}
+
+/* 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.
+ * \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_load work = *gtlv;
+ for (;;) {
+ int rc = osmo_gtlv_load_next(&work);
+ if (rc)
+ return rc;
+ if (!work.val)
+ return -ENOENT;
+ if (work.tag == tag) {
+ *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)
+{
+ 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), tag, len, gtlv);
+ if (rc < 0)
+ return rc;
+ if (rc > 0)
+ msgb_put(gtlv->dst, rc);
+ gtlv->last_tag = tag;
+ 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, gtlv->last_tag, 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->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, unsigned int tag, size_t len,
+ struct osmo_gtlv_put *gtlv)
+{
+ if (tag > UINT8_MAX)
+ return -EINVAL;
+ if (len > UINT8_MAX)
+ return -EMSGSIZE;
+ if (dst_data_avail < 2)
+ return -ENOSPC;
+ dst_data[0] = 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->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, unsigned int tag, size_t len,
+ struct osmo_gtlv_put *gtlv)
+{
+ if (tag > UINT16_MAX)
+ return -EINVAL;
+ if (len > UINT16_MAX)
+ return -EMSGSIZE;
+ if (dst_data_avail < 4)
+ return -ENOSPC;
+ osmo_store16be(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/tests/Makefile.am b/tests/Makefile.am
index 280c468..01cbed5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,4 +1,5 @@
SUBDIRS = \
+ libosmo-gtlv \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
diff --git a/tests/libosmo-gtlv/Makefile.am b/tests/libosmo-gtlv/Makefile.am
new file mode 100644
index 0000000..7ab4ae0
--- /dev/null
+++ b/tests/libosmo-gtlv/Makefile.am
@@ -0,0 +1,30 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gtlv_test \
+ $(NULL)
+
+EXTRA_DIST = \
+ gtlv_test.ok \
+ $(NULL)
+
+gtlv_test_SOURCES = \
+ gtlv_test.c \
+ $(NULL)
+
+gtlv_test_LDADD = \
+ $(top_builddir)/src/libosmo-gtlv/libosmo-gtlv.a \
+ $(LIBOSMOCORE_LIBS) \
+ $(NULL)
+
+.PHONY: update_exp
+update_exp:
+ $(builddir)/gtlv_test >$(srcdir)/gtlv_test.ok
diff --git a/tests/libosmo-gtlv/gtlv_test.c b/tests/libosmo-gtlv/gtlv_test.c
new file mode 100644
index 0000000..c009850
--- /dev/null
+++ b/tests/libosmo-gtlv/gtlv_test.c
@@ -0,0 +1,513 @@
+/*
+ * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved.
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gtlv/gtlv.h>
+
+void *ctx;
+
+struct ie {
+ int tag;
+ 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_tl(&gtlv, ie->tag, 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(&gtlv) == 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(&gtlv);
+
+ for (ie = ies; ie->val; ie++) {
+ int rc = osmo_gtlv_load_next(&gtlv);
+ 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=%d L=%zu v=%s\n", gtlv.tag, gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len));
+ if (gtlv.tag != ie->tag) {
+ printf(" ERROR loading TLV structure: expected tag %d, got tag %d\n", ie->tag, gtlv.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(&gtlv);
+
+ ie = ies;
+ while (1) {
+ int rc;
+ int next_tag = osmo_gtlv_load_peek_tag(&gtlv);
+ if (next_tag == -ENOENT)
+ printf(" peek T=-ENOENT\n");
+ else
+ printf(" peek T=%d\n", next_tag);
+
+ if (ie->val && next_tag != ie->tag) {
+ printf(" ERROR peeking tag: expected tag %d, got tag %d\n", ie->tag, next_tag);
+ exit(1);
+ }
+ if (!ie->val && next_tag != -ENOENT) {
+ printf(" ERROR peeking tag: expected -ENOENT, got tag %d\n", next_tag);
+ exit(1);
+ }
+
+ if (next_tag == -ENOENT)
+ break;
+
+ /* go to the next TLV */
+ rc = osmo_gtlv_load_next(&gtlv);
+ 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(&gtlv);
+ rc = osmo_gtlv_load_next_by_tag(&gtlv, ie->tag);
+ if (rc) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag(%d) rc = %d\n", ie->tag, rc);
+ exit(1);
+ }
+ if (!gtlv.val) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag(%d) returned NULL val\n",
+ ie->tag);
+ exit(1);
+ }
+ if (gtlv.tag != ie->tag) {
+ printf(" ERROR loading TLV structure: expected tag %d, got tag %d\n", ie->tag, gtlv.tag);
+ exit(1);
+ }
+ if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) {
+ while (1) {
+ printf(" (mismatch: T=%d L=%zu v=%s, checking for another occurrence of T=%d)\n",
+ gtlv.tag, gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len), gtlv.tag);
+ rc = osmo_gtlv_load_next_by_tag(&gtlv, ie->tag);
+ 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=%d L=%zu v=%s\n", gtlv.tag, gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len));
+ }
+
+ printf("- decoding every second tag:\n");
+
+ osmo_gtlv_load_start(&gtlv);
+ for (ie = ies; ie->val; ie++) {
+ /* skip one tag */
+ ie++;
+ if (!ie->val)
+ break;
+
+ rc = osmo_gtlv_load_next_by_tag(&gtlv, ie->tag);
+ if (rc) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag(%d) rc = %d\n", ie->tag, rc);
+ exit(1);
+ }
+ if (!gtlv.val) {
+ printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag(%d) returned NULL val\n",
+ ie->tag);
+ exit(1);
+ }
+ if (gtlv.tag != ie->tag) {
+ printf(" ERROR loading TLV structure: expected tag %d, got tag %d\n", ie->tag, gtlv.tag);
+ exit(1);
+ }
+ if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) {
+ while (1) {
+ printf(" (mismatch: T=%d L=%zu v=%s, checking for another occurrence of T=%d)\n",
+ gtlv.tag, gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len), gtlv.tag);
+ rc = osmo_gtlv_load_next_by_tag(&gtlv, ie->tag);
+ 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=%d L=%zu v=%s\n", gtlv.tag, 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(&gtlv, ie->tag);
+ printf(" osmo_gtlv_load_next_by_tag(%d) rc=", ie->tag);
+ 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 */
+ { 0, "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 */
+ { 0, "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 */
+ { 0, "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->tag = (((int)pos[1]) << 7) + (pos[0] & 0x7f);
+ pos += 2;
+ } else {
+ gtlv->tag = pos[0];
+ pos++;
+ }
+
+ switch (gtlv->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, unsigned int tag, size_t len, struct osmo_gtlv_put *gtlv)
+{
+ uint8_t *pos = dst_data;
+ uint8_t *end = dst_data + dst_data_avail;
+ 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("txlxv_tests", txlxv_tests, ARRAY_SIZE(txlxv_tests), &txlxv_cfg);
+}
+
+int main()
+{
+ ctx = talloc_named_const(NULL, 0, "gtlv_test");
+ msgb_talloc_ctx_init(ctx, 0);
+
+ test_t8l8v();
+ test_t16l16v();
+ test_txlxv();
+
+ 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..3cebd7e
--- /dev/null
+++ b/tests/libosmo-gtlv/gtlv_test.ok
@@ -0,0 +1,256 @@
+
+=== 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 T=-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(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 T=-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(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 T=-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(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 T=-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(1042) rc=-ENOENT
+=== end: test_t16l16v[1]
+
+=== start: txlxv_tests[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 T=-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(0) rc=-ENOENT
+=== end: txlxv_tests[0]
+
+=== start: txlxv_tests[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 T=-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(1042) rc=-ENOENT
+=== end: txlxv_tests[1]
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 09a77c3..711f981 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -1,2 +1,8 @@
AT_INIT
AT_BANNER([Regression tests.])
+
+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