/* * (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 void *ctx; struct ie { struct osmo_gtlv_tag_inst ti; 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_tli(>lv, &ie->ti, 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(>lv) == 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(>lv); for (ie = ies; ie->val; ie++) { int rc = osmo_gtlv_load_next(>lv); 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=%s L=%zu", osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL), gtlv.len); printf(" v=%s\n", osmo_hexdump_nospc(gtlv.val, gtlv.len)); if (gtlv.ti.tag != ie->ti.tag) { printf(" ERROR loading TLV structure: expected tag %u, got tag %u\n", ie->ti.tag, gtlv.ti.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(>lv); ie = ies; while (1) { int rc; struct osmo_gtlv_tag_inst next_tag; rc = osmo_gtlv_load_peek_tag(>lv, &next_tag); if (rc == -ENOENT) { printf(" peek rc=-ENOENT\n"); } else { printf(" peek T=%s", osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag, NULL)); printf("\n"); } if (ie->val && osmo_gtlv_tag_inst_cmp(&next_tag, &ie->ti)) { printf(" ERROR peeking tag: expected tag %s, got tag %s\n", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag, NULL)); exit(1); } if (!ie->val && rc != -ENOENT) { printf(" ERROR peeking tag: expected -ENOENT, got rc=%d, tag %s\n", rc, osmo_gtlv_tag_inst_to_str_c(ctx, &next_tag, NULL)); exit(1); } if (rc == -ENOENT) break; /* go to the next TLV */ rc = osmo_gtlv_load_next(>lv); 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(>lv); rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti); if (rc) { printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) rc = %d\n", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), rc); exit(1); } if (!gtlv.val) { printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) returned NULL val\n", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL)); exit(1); } if (osmo_gtlv_tag_inst_cmp(>lv.ti, &ie->ti)) { printf(" ERROR loading TLV structure: expected tag %s, got tag %s\n", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL)); exit(1); } if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) { while (1) { printf(" (mismatch: T=%s L=%zu v=%s, checking for another occurrence of T=%s)\n", osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL), gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len), osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL)); rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti); 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=%s L=%zu v=%s\n", osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL), gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len)); } printf("- decoding every second tag:\n"); osmo_gtlv_load_start(>lv); for (ie = ies; ie->val; ie++) { /* skip one tag */ ie++; if (!ie->val) break; rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti); if (rc) { printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) rc = %d\n", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), rc); exit(1); } if (!gtlv.val) { printf(" ERROR loading TLV structure: osmo_gtlv_load_next_by_tag_inst(%s) returned NULL val\n", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL)); exit(1); } if (osmo_gtlv_tag_inst_cmp(>lv.ti, &ie->ti)) { printf(" ERROR loading TLV structure: expected tag %s, got tag %s\n", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL), osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL)); exit(1); } if (strcmp(ie->val, osmo_hexdump_nospc(gtlv.val, gtlv.len))) { while (1) { printf(" (mismatch: T=%s L=%zu v=%s, checking for another occurrence of T=%s)\n", osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL), gtlv.len, osmo_hexdump_nospc(gtlv.val, gtlv.len), osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL)); rc = osmo_gtlv_load_next_by_tag_inst(>lv, &ie->ti); 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=%s L=%zu v=%s\n", osmo_gtlv_tag_inst_to_str_c(ctx, >lv.ti, NULL), 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_inst(>lv, &ie->ti); printf(" osmo_gtlv_load_next_by_tag_inst(%s) rc=", osmo_gtlv_tag_inst_to_str_c(ctx, &ie->ti, NULL)); 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 */ { {}, "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(void) { test_tlv(__func__, t8l8v_tests, ARRAY_SIZE(t8l8v_tests), &osmo_t8l8v_cfg); } struct ie t16l16v_test1[] = { /* smallest T */ { {}, "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(void) { test_tlv(__func__, t16l16v_tests, ARRAY_SIZE(t16l16v_tests), &osmo_t16l16v_cfg); } struct ie txlxv_test1[] = { /* smallest T */ { {}, "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->ti.tag = (((int)pos[1]) << 7) + (pos[0] & 0x7f); pos += 2; } else { gtlv->ti.tag = pos[0]; pos++; } switch (gtlv->ti.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, const struct osmo_gtlv_tag_inst *ti, size_t len, struct osmo_gtlv_put *gtlv) { uint8_t *pos = dst_data; uint8_t *end = dst_data + dst_data_avail; unsigned int tag = ti->tag; 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(void) { test_tlv(__func__, txlxv_tests, ARRAY_SIZE(txlxv_tests), &txlxv_cfg); } /* Example of defining a TLI, with an instance indicator */ static int tliv_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->ti.tag = src_data[0]; gtlv->len = src_data[1]; switch (gtlv->ti.tag) { /* All tags that are TLIV go here */ case 5: case 7: case 9: if (src_data_len < 3) return -ENOSPC; gtlv->ti.instance_present = true; gtlv->ti.instance = src_data[2]; gtlv->val = src_data + 3; return 0; default: gtlv->val = src_data + 2; return 0; } } static int tliv_store_tl(uint8_t *dst_data, size_t dst_data_avail, const struct osmo_gtlv_tag_inst *ti, size_t len, struct osmo_gtlv_put *gtlv) { if (ti->tag > UINT8_MAX) return -EINVAL; if (len > UINT8_MAX) return -EMSGSIZE; if (dst_data_avail < 2) return -ENOSPC; dst_data[0] = ti->tag; dst_data[1] = len; switch (ti->tag) { /* All tags that are TLIV go here */ case 5: case 7: case 9: if (dst_data_avail < 3) return -ENOSPC; if (!ti->instance_present) return -EINVAL; if (ti->instance > UINT8_MAX) return -EINVAL; dst_data[2] = ti->instance; return 3; default: return 2; } } const struct osmo_gtlv_cfg osmo_tliv_cfg = { .tl_min_size = 2, .load_tl = tliv_load_tl, .store_tl = tliv_store_tl, }; struct ie tliv_test1[] = { /* TLV */ { {1}, "0002" }, /* TLIV */ { {5, true, 1}, "0017" }, /* TLIV */ { {5, true, 2}, "0018" }, /* TLIV */ { {5, true, 3}, "0019" }, /* TLV */ { {6}, "001a" }, /* TLIV */ { {7, true, 1}, "001b" }, /* TLIV */ { {9, true, 1}, "001c" }, {} }; struct ie *tliv_tests[] = { tliv_test1, }; void test_tliv(void) { test_tlv(__func__, tliv_tests, ARRAY_SIZE(tliv_tests), &osmo_tliv_cfg); } int main(int argc, char **argv) { ctx = talloc_named_const(NULL, 0, "gtlv_test"); msgb_talloc_ctx_init(ctx, 0); test_t8l8v(); test_t16l16v(); test_txlxv(); test_tliv(); talloc_free(ctx); return 0; }