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