From 8006f5393e21750558a01c780641831d925382ee Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Wed, 13 Feb 2019 22:23:13 +0100 Subject: TLV: Add one-shot TLV encoder So far, the TLV code contained two types of functions * tlp_parse() to parse all TLVs according to definition into tlvp_parsed * various helper functions to encode individual TLVs during message generation This patch implements the inverse of tlv_parse(): tlv_encode(), which takes a full 'struct tlv_pared' and encodes all IEs found in it. The order of IEs is in numerically ascending order of the tag. As many protocols have different IE/TLV ordering requirements, let's add a tlv_encode_ordered() function where the caller can specify the TLV ordering during the one-shot encode. Change-Id: I761a30bf20355a9f80a4a8e0c60b0b0f78515efe --- include/osmocom/gsm/tlv.h | 6 +++ src/gsm/libosmogsm.map | 3 ++ src/gsm/tlv_parser.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++ tests/tlv/tlv_test.c | 47 +++++++++++++++++++++++ tests/tlv/tlv_test.ok | 2 + 5 files changed, 155 insertions(+) diff --git a/include/osmocom/gsm/tlv.h b/include/osmocom/gsm/tlv.h index d0c95522..bb0e8fc9 100644 --- a/include/osmocom/gsm/tlv.h +++ b/include/osmocom/gsm/tlv.h @@ -457,6 +457,12 @@ int tlv_parse2(struct tlv_parsed *dec, int dec_multiples, /* take a master (src) tlv def and fill up all empty slots in 'dst' */ void tlv_def_patch(struct tlv_definition *dst, const struct tlv_definition *src); +int tlv_encode_one(struct msgb *msg, enum tlv_type type, uint8_t tag, + unsigned int len, const uint8_t *val); +int tlv_encode(struct msgb *msg, const struct tlv_definition *def, const struct tlv_parsed *tp); +int tlv_encode_ordered(struct msgb *msg, const struct tlv_definition *def, const struct tlv_parsed *tp, + const uint8_t *tag_order, unsigned int tag_order_len); + #define TLVP_PRESENT(x, y) ((x)->lv[y].val) #define TLVP_LEN(x, y) (x)->lv[y].len #define TLVP_VAL(x, y) (x)->lv[y].val diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index 0f4a0db1..299504e8 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -537,6 +537,9 @@ tlv_dump; tlv_parse; tlv_parse2; tlv_parse_one; +tlv_encode; +tlv_encode_ordered; +tlv_encode_one; tvlv_att_def; vtvlv_gan_att_def; diff --git a/src/gsm/tlv_parser.c b/src/gsm/tlv_parser.c index 6e089f7f..159b42bd 100644 --- a/src/gsm/tlv_parser.c +++ b/src/gsm/tlv_parser.c @@ -120,6 +120,103 @@ int osmo_tlvp_merge(struct tlv_parsed *dst, const struct tlv_parsed *src) return 0; } + +/*! Encode a single TLV into given message buffer + * \param[inout] msg Caller-allocated message buffer with sufficient tailroom + * \param[in] type TLV type/format to use during encode + * \param[in] tag Tag of TLV to be encoded + * \parma[in] len Length of TLV to be encoded + * \param[in] val Value part of TLV to be encoded + * \returns 0 on success; negative in case of error */ +int tlv_encode_one(struct msgb *msg, enum tlv_type type, uint8_t tag, + unsigned int len, const uint8_t *val) +{ + switch (type) { + case TLV_TYPE_NONE: + break; + case TLV_TYPE_FIXED: + msgb_tv_fixed_put(msg, tag, len, val); + break; + case TLV_TYPE_T: + msgb_v_put(msg, tag); + break; + case TLV_TYPE_TV: + msgb_tv_put(msg, tag, val[0]); + break; + case TLV_TYPE_TLV: + msgb_tlv_put(msg, tag, len, val); + break; + case TLV_TYPE_TL16V: + msgb_tl16v_put(msg, tag, len, val); + break; + case TLV_TYPE_TvLV: + msgb_tvlv_put(msg, tag, len, val); + break; + case TLV_TYPE_SINGLE_TV: + msgb_v_put(msg, (tag << 4) | (val[0] & 0xf)); + break; + case TLV_TYPE_vTvLV_GAN: + msgb_vtvlv_gan_put(msg, tag, len, val); + break; + default: + return -EINVAL; + } + return 0; +} + +/*! Encode a set of decoded TLVs according to a given definition into a message buffer + * \param[inout] msg Caller-allocated message buffer with sufficient tailroom + * \param[in] def structure defining the valid TLV tags / configurations + * \param[in] tp decoded values to be encoded + * \returns number of bytes consumed in msg; negative in case of error */ +int tlv_encode(struct msgb *msg, const struct tlv_definition *def, const struct tlv_parsed *tp) +{ + unsigned int tailroom_before = msgb_tailroom(msg); + unsigned int i; + int rc; + + for (i = 0; i < ARRAY_SIZE(tp->lv); i++) { + /* skip entries in the array that aren't used/filled */ + if (!TLVP_PRESENT(tp, i)) + continue; + + rc = tlv_encode_one(msg, def->def[i].type, i, TLVP_LEN(tp, i), TLVP_VAL(tp, i)); + if (rc < 0) + return rc; + } + + return tailroom_before - msgb_tailroom(msg); +} + +/*! Encode a set of decoded TLVs according to a given definition and IE order into a message buffer + * \param[inout] msg Caller-allocated message buffer with sufficient tailroom + * \param[in] def structure defining the valid TLV tags / configurations + * \param[in] tp decoded values to be encoded + * \param[in] tag_order array of tags determining the IE encoding order + * \param[in] tag_order_len length of tag_order + * \returns number of bytes consumed in msg; negative in case of error */ +int tlv_encode_ordered(struct msgb *msg, const struct tlv_definition *def, const struct tlv_parsed *tp, + const uint8_t *tag_order, unsigned int tag_order_len) +{ + + unsigned int tailroom_before = msgb_tailroom(msg); + unsigned int i; + int rc; + + for (i = 0; i < tag_order_len; i++) { + uint8_t tag = tag_order[i]; + + /* skip entries in the array that aren't used/filled */ + if (!TLVP_PRESENT(tp, tag)) + continue; + + rc = tlv_encode_one(msg, def->def[tag].type, tag, TLVP_LEN(tp, tag), TLVP_VAL(tp, tag)); + if (rc < 0) + return rc; + } + return tailroom_before - msgb_tailroom(msg); +} + /*! Parse a single TLV encoded IE * \param[out] o_tag the tag of the IE that was found * \param[out] o_len length of the IE that was found diff --git a/tests/tlv/tlv_test.c b/tests/tlv/tlv_test.c index e2065b00..925d7628 100644 --- a/tests/tlv/tlv_test.c +++ b/tests/tlv/tlv_test.c @@ -1,4 +1,6 @@ +#include #include +#include static void check_tlv_parse(uint8_t **data, size_t *data_len, uint8_t exp_tag, size_t exp_len, const uint8_t *exp_val) @@ -286,12 +288,57 @@ static void test_tlv_repeated_ie() OSMO_ASSERT(dec3[2].lv[tag].val == &test_data[2 + 3 + 3]); } +static void test_tlv_encoder() +{ + const uint8_t enc_ies[] = { + 0x17, 0x14, 0x06, 0x2b, 0x12, 0x2b, 0x0b, 0x40, 0x2b, 0xb7, 0x05, 0xd0, 0x63, 0x82, 0x95, 0x03, 0x05, 0x40, + 0x07, 0x08, 0x43, 0x90, + 0x2c, 0x04, + 0x40, 0x42, + }; + const uint8_t ie_order[] = { 0x2c, 0x40, 0x17 }; + const uint8_t enc_ies_reordered[] = { + 0x2c, 0x04, + 0x40, 0x42, + 0x17, 0x14, 0x06, 0x2b, 0x12, 0x2b, 0x0b, 0x40, 0x2b, 0xb7, 0x05, 0xd0, 0x63, 0x82, 0x95, 0x03, 0x05, 0x40, + 0x07, 0x08, 0x43, 0x90, + }; + struct tlv_parsed tp; + struct msgb *msg = msgb_alloc(1024, __func__); + int rc; + + printf("Testing TLV encoder by decoding + re-encoding binary\n"); + + OSMO_ASSERT(msg); + + /* decode BSSAP IEs specified above */ + rc = osmo_bssap_tlv_parse(&tp, enc_ies, ARRAY_SIZE(enc_ies)); + OSMO_ASSERT(rc == 3); + + /* re-encode it */ + rc = tlv_encode(msg, gsm0808_att_tlvdef(), &tp); + OSMO_ASSERT(rc == ARRAY_SIZE(enc_ies)); + OSMO_ASSERT(!memcmp(msgb_data(msg), enc_ies, ARRAY_SIZE(enc_ies))); + + msgb_reset(msg); + + printf("Testing TLV encoder with IE ordering\n"); + + /* re-encodei in different order */ + rc = tlv_encode_ordered(msg, gsm0808_att_tlvdef(), &tp, ie_order, ARRAY_SIZE(ie_order)); + OSMO_ASSERT(rc == ARRAY_SIZE(enc_ies)); + OSMO_ASSERT(!memcmp(msgb_data(msg), enc_ies_reordered, ARRAY_SIZE(enc_ies_reordered))); + + msgb_free(msg); +} + int main(int argc, char **argv) { //osmo_init_logging2(ctx, &info); test_tlv_shift_functions(); test_tlv_repeated_ie(); + test_tlv_encoder(); printf("Done.\n"); return EXIT_SUCCESS; diff --git a/tests/tlv/tlv_test.ok b/tests/tlv/tlv_test.ok index de159bfb..f3f0fd41 100644 --- a/tests/tlv/tlv_test.ok +++ b/tests/tlv/tlv_test.ok @@ -1,2 +1,4 @@ Test shift functions +Testing TLV encoder by decoding + re-encoding binary +Testing TLV encoder with IE ordering Done. -- cgit v1.2.3