From f842c8c8d0d809b298668c9f547e6df837532fce Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Wed, 12 Jan 2022 02:57:58 +0100 Subject: libosmo-gtlv: add auto dec/enc to/from structs Add osmo_gtlv_coding: describe the value part of a TLV (decode and encode), describe a struct with its members, and get/put readily decoded structs from/to a raw PDU, directly. With osmo_gtlv_coding defined for a protocol's tags, we only deal with encoded PDUs or fully decoded C structs, no TLV related re-implementations clutter up the message handling code. A usage example is given in gtlv_dec_enc_test. The first real use will be the PFCP protocol in osmo-upf.git. With osmo_gtlv_coding, there still is a lot of monkey work involved in describing the decoded structs. A subsequent patch adds a generator for osmo_gtlv_coding and message structs from tag value lists. Related: SYS#5599 Change-Id: I65de793105882a452124ee58adb0e58469e6e796 --- include/osmocom/gtlv/Makefile.am | 1 + include/osmocom/gtlv/gtlv_dec_enc.h | 201 ++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 include/osmocom/gtlv/gtlv_dec_enc.h (limited to 'include') diff --git a/include/osmocom/gtlv/Makefile.am b/include/osmocom/gtlv/Makefile.am index f922ab8..a7c10a6 100644 --- a/include/osmocom/gtlv/Makefile.am +++ b/include/osmocom/gtlv/Makefile.am @@ -1,5 +1,6 @@ tlv_HEADERS = \ gtlv.h \ + gtlv_dec_enc.h \ $(NULL) tlvdir = $(includedir)/osmocom/gtlv diff --git a/include/osmocom/gtlv/gtlv_dec_enc.h b/include/osmocom/gtlv/gtlv_dec_enc.h new file mode 100644 index 0000000..b861129 --- /dev/null +++ b/include/osmocom/gtlv/gtlv_dec_enc.h @@ -0,0 +1,201 @@ +/* Decode and encode the value parts of a TLV structure */ +/* + * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr + * + * 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 . + * + */ + +#pragma once + +#include + +#include + +struct value_string; + +/* User defined function to decode a single TLV value part. See struct osmo_gtlv_coding. + * \param decoded_struct Pointer to the root struct, as context information, e.g. for logging. + * \param decode_to Pointer to the struct member, write the decoded value here. + * \param gtlv TLV loader, pointing at a gtlv->val of gtlv->len bytes. + * \return 0 on success, nonzero on error, e.g. -EINVAL if the gtlv->val is invalid. + */ +typedef int (*osmo_gtlv_dec_func)(void *decoded_struct, void *decode_to, const struct osmo_gtlv_load *gtlv); + +/* User defined function to encode a single TLV value part. See struct osmo_gtlv_coding. + * \param gtlv TLV writer, pointing at a gtlv->dst to msgb_put() data in. + * \param decoded_struct Pointer to the root struct, as context information, e.g. for logging. + * \param encode_from Pointer to the struct member, obtain the value to encode from here. + * \return 0 on success, nonzero on error, e.g. -EINVAL if encode_from has an un-encodable value. + */ +typedef int (*osmo_gtlv_enc_func)(struct osmo_gtlv_put *gtlv, const void *decoded_struct, const void *encode_from); + +/* Optional user defined function to convert a decoded IE struct (the Value part stored as C struct) to string. See + * struct osmo_gtlv_coding. + * \param buf Return string in this buffer. + * \param buflen Size of buf. + * \param str_of Pointer to the struct member described by an osmo_gtlv_coding, obtain the value to encode from here. + * \return number of characters that would be written if the buffer is large enough, like snprintf(). + */ +typedef int (*osmo_gtlv_enc_to_str_func)(char *buf, size_t buflen, const void *str_of); + +/* Whether TLV structures nested inside the value data of an outer IE should be parsed in the same order. */ +enum osmo_gtlv_coding_nested_ies_ordered { + /*! When stepping into nested IEs, keep the same ordering requirement as the outer IE. */ + OSMO_GTLV_NESTED_IES_ORDERING_SAME = 0, + /*! Require IEs in a PDU to appear exactly in the order defined by osmo_gtlv_coding arrays. Causes a parsing + * failure if the TLVs appear in a different order. Does much less iterating looking for matching tags when + * decoding (faster). */ + OSMO_GTLV_NESTED_IES_ORDERED, + /*! Do not require IEs to be in the defined order in decoded PDUs. When encoding a TLV, IEs will always be + * encoded in the order they are defined. This has an effect on decoding only. */ + OSMO_GTLV_NESTED_IES_UNORDERED, +}; + +#define OSMO_ARRAY_PITCH(arr) ((char *)(&(arr)[1]) - (char *)(arr)) +#define OSMO_MEMB_ARRAY_PITCH(obj_type, arr_memb) OSMO_ARRAY_PITCH(((obj_type *)0)->arr_memb) + +/*! Definition of how to decode/encode a IE to/from a struct. + * Kept in lists describing TLV structures, and nestable. + * + * Instance lists of this can be composed manually, or auto-generated using gtlv_gen.c. Auto-generating has the benefit + * that the decoded structs to match the IEs are also generated at the same time and thus always match the message + * definitions. For an example, see tests/libosmo-gtlv/test_gtlv_gen/. */ +struct osmo_gtlv_coding { + /*! the IEI value */ + unsigned int tag; + + /*! Decoding function callback. Invoked for each defined and present IE encountered in the message. + * Return 0 on success, negative on failure. */ + osmo_gtlv_dec_func dec_func; + /*! Encoding function callback. Invoked for each defined and present IE encountered in the message. + * Return 0 on success, negative on failure. */ + osmo_gtlv_enc_func enc_func; + + /*! Means to output the decoded value to a human readable string, optional. */ + osmo_gtlv_enc_to_str_func enc_to_str_func; + + /*! offsetof(decoded_struct_type, member_var): how far into the base struct you find a specific field for decoded + * value. For example, memb_ofs = offsetof(struct foo_msg, ies.bar_response.cause). + * When decoding, the decoded value is written here, when encoding it is read from here. */ + unsigned int memb_ofs; + /*! For repeated IEs (.has_count = true), the array pitch / the offset to add to get to the next array index. */ + unsigned int memb_array_pitch; + + /*! True for optional/conditional IEs. */ + bool has_presence_flag; + /* For optional/conditional IEs (has_presence_flag = true), the offset of the bool foo_present flag, + * For example, if there are + * + * struct foo_msg { + * struct baz baz; + * bool baz_present; + * }; + * + * then set + * memb_ofs = offsetof(struct foo_msg, baz); + * has_presence_flag = true; + * presence_flag_ofs = offsetof(struct foo_msg, baz_present); + */ + unsigned int presence_flag_ofs; + + /*! True for repeated IEs, for array members: + * + * struct foo_msg { + * struct moo moo[10]; + * unsigned int moo_count; + * }; + * + * memb_ofs = offsetof(struct foo_msg, moo); + * has_count = true; + * count_ofs = offsetof(struct foo_msg, moo_count); + * count_max = 10; + */ + bool has_count; + /*! For repeated IEs, the offset of the unsigned int foo_count indicator of how many array indexes are + * in use. See has_count. */ + unsigned int count_ofs; + /*! Maximum array size for member_var[]. See has_count. */ + unsigned int count_max; + /*! If nonzero, it is an error when less than this amount of the repeated IE have been decoded. */ + unsigned int count_mandatory; + + /*! For nested TLVs: if this IE's value part is itself a separate TLV structure, point this at the list of IE + * coding definitions for the inner IEs. + * In this example, the nested IEs decode/encode to different sub structs depending on the tag value. + * + * struct bar { + * int aaa; + * int bbb; + * }; + * + * struct foo_msg { + * struct bar bar; + * struct bar other_bar; + * }; + * + * struct osmo_gtlv_coding bar_nested_ies[] = { + * { FOO_IEI_AAA, .memb_ofs = offsetof(struct bar, aaa), }, + * { FOO_IEI_BBB, .memb_ofs = offsetof(struct bar, bbb), }, + * {} + * }; + * + * struct osmo_gtlv_coding foo_msg_ies[] = { + * { FOO_IEI_GOO, .memb_ofs = offsetof(struct foo_msg, bar), .nested_ies = bar_nested_ies, }, + * { FOO_IEI_OTHER_GOO, .memb_ofs = offsetof(struct foo_msg, other_bar), .nested_ies = bar_nested_ies, }, + * {} + * }; + */ + const struct osmo_gtlv_coding *nested_ies; + + /*! If the nested TLV has a different tag/length size than the outer TLV structure, provide a different config + * here. If they are the same, just keep this NULL. */ + const struct osmo_gtlv_cfg *nested_ies_cfg; + + /*! When stepping into nested IEs, what is the ordering requirement for the nested TLV structure? */ + enum osmo_gtlv_coding_nested_ies_ordered nested_ies_ordered; +}; + + +/*! User defined hook for error logging during TLV and value decoding. + * \param decoded_struct Pointer to the base struct describing this message, for context. + * \param file Source file of where the error occurred. + * \param line Source file line of where the error occurred. + * \param fmt Error message string format. + * \param ... Error message string args. + */ +typedef void (*osmo_gtlv_err_cb)(void *data, void *decoded_struct, const char *file, int line, const char *fmt, ...); + +int osmo_gtlvs_decode(void *decoded_struct, unsigned int obj_ofs, struct osmo_gtlv_load *gtlv, bool tlv_ordered, + const struct osmo_gtlv_coding *ie_coding, + osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs); + +int osmo_gtlvs_encode(struct osmo_gtlv_put *gtlv, const void *decoded_struct, unsigned int obj_ofs, + const struct osmo_gtlv_coding *ie_coding, + osmo_gtlv_err_cb err_cb, void *err_cb_data, const struct value_string *iei_strs); + +int osmo_gtlvs_encode_to_str_buf(char *buf, size_t buflen, const void *decoded_struct, unsigned int obj_ofs, + const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs); +char *osmo_gtlvs_encode_to_str_c(void *ctx, const void *decoded_struct, unsigned int obj_ofs, + const struct osmo_gtlv_coding *ie_coding, const struct value_string *iei_strs); + +static inline bool osmo_gtlv_coding_end(const struct osmo_gtlv_coding *iec) +{ + return iec->dec_func == NULL && iec->enc_func == NULL && iec->nested_ies == NULL; +} -- cgit v1.2.3