fixeria has submitted this change. ( https://gerrit.osmocom.org/c/libosmo-gprs/+/29482 )
Change subject: llc: implement Exchange Identification (XID) codec ......................................................................
llc: implement Exchange Identification (XID) codec
Change-Id: I16b3f7ab82c489144098aad0dccc4c67d8382c67 --- M include/osmocom/gprs/llc/llc.h M src/llc/llc_xid.c M tests/llc/pdu_codec_test.c M tests/llc/pdu_codec_test.ok 4 files changed, 403 insertions(+), 0 deletions(-)
Approvals: Jenkins Builder: Verified fixeria: Looks good to me, approved
diff --git a/include/osmocom/gprs/llc/llc.h b/include/osmocom/gprs/llc/llc.h index 0e4531e..e27fb8a 100644 --- a/include/osmocom/gprs/llc/llc.h +++ b/include/osmocom/gprs/llc/llc.h @@ -95,6 +95,17 @@ return get_value_string(osmo_gprs_llc_xid_type_names, val); }
+struct osmo_gprs_llc_xid_field { + enum osmo_gprs_llc_xid_type type; + /* Fixed-length value */ + uint32_t val; + /* Variable-length value */ + struct { + const uint8_t *val; + uint8_t val_len; + } var; +}; + /* Section 4.5.2 Logical Link States + Annex C.2 */ enum osmo_gprs_llc_lle_state { OSMO_GPRS_LLC_LLES_UNASSIGNED = 1, /* No TLLI yet */ @@ -158,4 +169,12 @@
uint32_t osmo_gprs_llc_fcs(const uint8_t *data, size_t len);
+bool osmo_gprs_llc_xid_field_is_valid(const struct osmo_gprs_llc_xid_field *field); +int osmo_gprs_llc_xid_decode(struct osmo_gprs_llc_xid_field *fields, + unsigned int max_fields, + const uint8_t *data, size_t data_len); +int osmo_gprs_llc_xid_encode(struct msgb *msg, + const struct osmo_gprs_llc_xid_field *fields, + unsigned int num_fields); + void osmo_gprs_llc_set_log_cat(int cat); diff --git a/src/llc/llc_xid.c b/src/llc/llc_xid.c index fb84137..a787e1a 100644 --- a/src/llc/llc_xid.c +++ b/src/llc/llc_xid.c @@ -20,10 +20,17 @@ * */
+#include <errno.h> +#include <stdint.h> + +#include <osmocom/core/msgb.h> #include <osmocom/core/utils.h> +#include <osmocom/core/logging.h>
#include <osmocom/gprs/llc/llc.h>
+extern int g_log_cat; + const struct value_string osmo_gprs_llc_xid_type_names[] = { { OSMO_GPRS_LLC_XID_T_VERSION, "LLC-Version" }, { OSMO_GPRS_LLC_XID_T_IOV_UI, "IOV-UI" }, @@ -43,3 +50,212 @@ { OSMO_GPRS_LLC_XID_T_MAC_IOV_UI, "MAC-IOV-UI" }, { 0, NULL } }; + +/* Table 6: LLC layer parameter negotiation */ +static const struct { + bool var_len; + uint8_t len; + unsigned int min; + unsigned int max; + bool allow_zero; +} gprs_llc_xid_desc[] = { + [OSMO_GPRS_LLC_XID_T_VERSION] = { .len = 1, .min = 0, .max = 15 }, + [OSMO_GPRS_LLC_XID_T_IOV_UI] = { .len = 4, .min = 0, .max = UINT32_MAX }, + [OSMO_GPRS_LLC_XID_T_IOV_I] = { .len = 4, .min = 0, .max = UINT32_MAX }, + [OSMO_GPRS_LLC_XID_T_T200] = { .len = 2, .min = 0, .max = 4095 }, + [OSMO_GPRS_LLC_XID_T_N200] = { .len = 1, .min = 1, .max = 15 }, + [OSMO_GPRS_LLC_XID_T_N201_U] = { .len = 2, .min = 140, .max = 1520 }, + [OSMO_GPRS_LLC_XID_T_N201_I] = { .len = 2, .min = 140, .max = 1520 }, + [OSMO_GPRS_LLC_XID_T_mD] = { .len = 2, .min = 9, .max = 24320, .allow_zero = true }, + [OSMO_GPRS_LLC_XID_T_mU] = { .len = 2, .min = 9, .max = 24320, .allow_zero = true }, + [OSMO_GPRS_LLC_XID_T_kD] = { .len = 1, .min = 1, .max = 255 }, + [OSMO_GPRS_LLC_XID_T_kU] = { .len = 1, .min = 1, .max = 255 }, + [OSMO_GPRS_LLC_XID_T_L3_PAR] = { .var_len = true }, + [OSMO_GPRS_LLC_XID_T_RESET] = { .len = 0 }, + [OSMO_GPRS_LLC_XID_T_IIOV_UI] = { .len = 4, .min = 0, .max = UINT32_MAX }, + [OSMO_GPRS_LLC_XID_T_IIOV_UI_CNT] = { .len = 1, .min = 1, .max = 255 }, + [OSMO_GPRS_LLC_XID_T_MAC_IOV_UI] = { .len = 4, .min = 0, .max = UINT32_MAX }, +}; + +bool osmo_gprs_llc_xid_field_is_valid(const struct osmo_gprs_llc_xid_field *field) +{ + if (field->type >= ARRAY_SIZE(gprs_llc_xid_desc)) { + LOGP(g_log_cat, LOGL_ERROR, + "Unknown XID field type 0x%02x\n", field->type); + return false; + } + + if (gprs_llc_xid_desc[field->type].var_len) + return true; + + /* For mU and mD, the value range (9 .. 24320) also includes 0 */ + if (gprs_llc_xid_desc[field->type].allow_zero && field->val == 0) + return true; + + if (field->val < gprs_llc_xid_desc[field->type].min) { + LOGP(g_log_cat, LOGL_ERROR, + "XID field %s value=%u < min=%u\n", + osmo_gprs_llc_xid_type_name(field->type), + field->val, gprs_llc_xid_desc[field->type].min); + return false; + } + + if (field->val > gprs_llc_xid_desc[field->type].max) { + LOGP(g_log_cat, LOGL_ERROR, + "XID field %s value=%u > max=%u\n", + osmo_gprs_llc_xid_type_name(field->type), + field->val, gprs_llc_xid_desc[field->type].max); + return false; + } + + return true; +} + +int osmo_gprs_llc_xid_encode(struct msgb *msg, + const struct osmo_gprs_llc_xid_field *fields, + unsigned int num_fields) +{ + for (unsigned int i = 0; i < num_fields; i++) { + const struct osmo_gprs_llc_xid_field *field = &fields[i]; + uint8_t *hdr, len; + + if (!osmo_gprs_llc_xid_field_is_valid(field)) + return -EINVAL; + + /* XID field type */ + hdr = msgb_put(msg, 1); + hdr[0] = (field->type & 0x1f) << 2; + + /* XID field length */ + if (gprs_llc_xid_desc[field->type].var_len) + len = field->var.val_len; + else + len = gprs_llc_xid_desc[field->type].len; + + if (len == 0) + continue; + if (len < 4) { + hdr[0] |= len; + } else { + msgb_put(msg, 1); + hdr[0] |= (1 << 7); /* XL=1 */ + hdr[0] |= (len >> 6) & 0x03; + hdr[1] = (len << 2) & 0xff; + } + + /* XID field value (variable length) */ + if (gprs_llc_xid_desc[field->type].var_len) { + memcpy(msgb_put(msg, len), field->var.val, len); + } else { + switch (len) { + case 1: + msgb_put_u8(msg, field->val); + break; + case 2: + osmo_store16be(field->val, msgb_put(msg, 2)); + break; + case 4: + osmo_store32be(field->val, msgb_put(msg, 4)); + break; + default: + /* Shall not happen */ + OSMO_ASSERT(0); + } + } + } + + return 0; +} + +int osmo_gprs_llc_xid_decode(struct osmo_gprs_llc_xid_field *fields, + unsigned int max_fields, + const uint8_t *data, size_t data_len) +{ + const uint8_t *ptr = &data[0]; + unsigned int num_fields = 0; + +#define check_len(len, text) \ + do { \ + if (data_len < (len)) { \ + LOGP(g_log_cat, LOGL_ERROR, "Failed to parse XID: %s\n", text); \ + return -EINVAL; \ + } \ + } while (0) + + while (data_len > 0) { + struct osmo_gprs_llc_xid_field *field = &fields[num_fields++]; + uint8_t len; + + if (num_fields > max_fields) { + LOGP(g_log_cat, LOGL_ERROR, + "Failed to parse XID: too many fields\n"); + return -ENOMEM; + } + + check_len(1, "short read at XID header"); + data_len -= 1; + + /* XID field type */ + field->type = (*ptr >> 2) & 0x1f; + if (field->type >= ARRAY_SIZE(gprs_llc_xid_desc)) { + LOGP(g_log_cat, LOGL_ERROR, + "Failed to parse XID: unknown field type 0x%02x\n", field->type); + return -EINVAL; + } + + /* XID field length */ + if (*ptr & (1 << 7)) { + check_len(1, "short read"); + data_len -= 1; + len = (*(ptr++) & 0x07) << 6; + len |= (*(ptr++) >> 2); + } else { + len = *(ptr++) & 0x03; + } + + check_len(len, "short read at XID payload"); + data_len -= len; + + /* XID field value (variable length) */ + if (gprs_llc_xid_desc[field->type].var_len) { + field->var.val = len ? ptr : NULL; + field->var.val_len = len; + } else { + if (len != gprs_llc_xid_desc[field->type].len) { + LOGP(g_log_cat, LOGL_NOTICE, + "XID field %s has unusual length=%u (expected %u)\n", + osmo_gprs_llc_xid_type_name(field->type), + len, gprs_llc_xid_desc[field->type].len); + } + + switch (len) { + case 0: + field->val = 0; + break; + case 1: + field->val = *ptr; + break; + case 2: + field->val = osmo_load16be(ptr); + break; + case 4: + field->val = osmo_load32be(ptr); + break; + default: + LOGP(g_log_cat, LOGL_ERROR, + "Failed to parse XID: unsupported field (%s) length=%u\n", + osmo_gprs_llc_xid_type_name(field->type), len); + return -EINVAL; + } + + if (!osmo_gprs_llc_xid_field_is_valid(field)) + return -EINVAL; + } + + ptr += len; + } + +#undef check_len + + return num_fields; +} diff --git a/tests/llc/pdu_codec_test.c b/tests/llc/pdu_codec_test.c index ed4e8bd..f2418fe 100644 --- a/tests/llc/pdu_codec_test.c +++ b/tests/llc/pdu_codec_test.c @@ -77,6 +77,109 @@ printf("\n"); }
+static void print_xid_fields(const struct osmo_gprs_llc_xid_field *fields, + unsigned int num_fields) +{ + for (unsigned int i = 0; i < num_fields; i++) { + const struct osmo_gprs_llc_xid_field *field = &fields[i]; + + printf(" field[%02d]: type=0x%02x (%s)", + i, field->type, osmo_gprs_llc_xid_type_name(field->type)); + if (field->type == OSMO_GPRS_LLC_XID_T_L3_PAR) { + printf(" data[] (len=%u): %s\n", field->var.val_len, + osmo_hexdump_nospc(field->var.val, field->var.val_len)); + } else { + printf(" val=%u\n", field->val); + } + } +} + +static void test_xid_dec_enc(void) +{ + static const char * const testData[] = { + /* TODO: more test vectors */ + "25401602082c", + "16020825402c", + "01001601f41a05df", + "01001605f01a05f0ac18112233445566", + "01001601f41a02081e00002200002908", + }; + + struct msgb *msg = msgb_alloc(1024, "LLC-XID"); + OSMO_ASSERT(msg != NULL); + + for (unsigned int i = 0; i < ARRAY_SIZE(testData); i++) { + struct osmo_gprs_llc_xid_field fields[16] = { 0 }; + uint8_t xid[256]; + size_t xid_len; + int rc; + + printf("%s(): decoding testData[%u] = %s\n", __func__, i, testData[i]); + + rc = osmo_hexparse(testData[i], &xid[0], sizeof(xid)); + xid_len = strlen(testData[i]) / 2; + OSMO_ASSERT(rc == xid_len); + + rc = osmo_gprs_llc_xid_decode(fields, ARRAY_SIZE(fields), + &xid[0], xid_len); + printf(" osmo_gprs_llc_xid_decode() returns %d\n", rc); + + if (rc > 0) + print_xid_fields(&fields[0], rc); + else + continue; + + printf("%s(): encoding decoded testData[%u]\n", __func__, i); + + msgb_reset(msg); + rc = osmo_gprs_llc_xid_encode(msg, fields, rc); + printf(" osmo_gprs_llc_xid_encode() returns %d\n", rc); + printf(" osmo_gprs_llc_xid_encode(): %s\n", osmo_hexdump_nospc(msg->data, msg->len)); + printf(" memcmp() returns %d\n", memcmp(&xid, msg->data, xid_len)); + } + + msgb_free(msg); + printf("\n"); +} + +static void test_xid_enc_dec(void) +{ + struct osmo_gprs_llc_xid_field dec_fields[16] = { 0 }; + int rc; + + const uint8_t l3_params[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }; + const struct osmo_gprs_llc_xid_field test_fields[] = { + { .type = OSMO_GPRS_LLC_XID_T_VERSION, .val = 0 }, + { .type = OSMO_GPRS_LLC_XID_T_T200, .val = 4095 }, + { .type = OSMO_GPRS_LLC_XID_T_N200, .val = 10 }, + { .type = OSMO_GPRS_LLC_XID_T_IOV_I, .val = 0x42424242 }, + { .type = OSMO_GPRS_LLC_XID_T_IOV_UI, .val = 0xdeadbeef }, + { .type = OSMO_GPRS_LLC_XID_T_L3_PAR, + .var = { .val_len = sizeof(l3_params), .val = &l3_params[0] } }, + { .type = OSMO_GPRS_LLC_XID_T_RESET }, + }; + + struct msgb *msg = msgb_alloc(1024, "LLC-XID"); + OSMO_ASSERT(msg != NULL); + + printf("%s(): encoding hand-crafted testData\n", __func__); + + rc = osmo_gprs_llc_xid_encode(msg, &test_fields[0], ARRAY_SIZE(test_fields)); + printf(" osmo_gprs_llc_xid_encode() returns %d\n", rc); + printf(" osmo_gprs_llc_xid_encode(): %s\n", osmo_hexdump_nospc(msg->data, msg->len)); + + printf("%s(): decoding encoded hand-crafted testData\n", __func__); + + rc = osmo_gprs_llc_xid_decode(&dec_fields[0], ARRAY_SIZE(dec_fields), + msg->data, msg->len); + printf(" osmo_gprs_llc_xid_decode() returns %d\n", rc); + if (rc > 0) + print_xid_fields(&dec_fields[0], rc); + + msgb_free(msg); + printf("\n"); +} + static const struct log_info_cat test_log_categories[] = { }; static const struct log_info test_log_info = { .cat = test_log_categories, @@ -97,6 +200,8 @@ log_set_use_color(osmo_stderr_target, 0);
test_pdu_dec_enc(); + test_xid_dec_enc(); + test_xid_enc_dec();
talloc_free(tall_ctx); } diff --git a/tests/llc/pdu_codec_test.ok b/tests/llc/pdu_codec_test.ok index 691e983..664768a 100644 --- a/tests/llc/pdu_codec_test.ok +++ b/tests/llc/pdu_codec_test.ok @@ -38,3 +38,66 @@ osmo_gprs_llc_pdu_encode(): 03fb1604d216f984 memcmp() returns 0
+test_xid_dec_enc(): decoding testData[0] = 25401602082c + osmo_gprs_llc_xid_decode() returns 3 + field[00]: type=0x09 (kD) val=64 + field[01]: type=0x05 (N201-U) val=520 + field[02]: type=0x0b (L3-Params) data[] (len=0): +test_xid_dec_enc(): encoding decoded testData[0] + osmo_gprs_llc_xid_encode() returns 0 + osmo_gprs_llc_xid_encode(): 25401602082c + memcmp() returns 0 +test_xid_dec_enc(): decoding testData[1] = 16020825402c + osmo_gprs_llc_xid_decode() returns 3 + field[00]: type=0x05 (N201-U) val=520 + field[01]: type=0x09 (kD) val=64 + field[02]: type=0x0b (L3-Params) data[] (len=0): +test_xid_dec_enc(): encoding decoded testData[1] + osmo_gprs_llc_xid_encode() returns 0 + osmo_gprs_llc_xid_encode(): 16020825402c + memcmp() returns 0 +test_xid_dec_enc(): decoding testData[2] = 01001601f41a05df + osmo_gprs_llc_xid_decode() returns 3 + field[00]: type=0x00 (LLC-Version) val=0 + field[01]: type=0x05 (N201-U) val=500 + field[02]: type=0x06 (N201-I) val=1503 +test_xid_dec_enc(): encoding decoded testData[2] + osmo_gprs_llc_xid_encode() returns 0 + osmo_gprs_llc_xid_encode(): 01001601f41a05df + memcmp() returns 0 +test_xid_dec_enc(): decoding testData[3] = 01001605f01a05f0ac18112233445566 + osmo_gprs_llc_xid_decode() returns 4 + field[00]: type=0x00 (LLC-Version) val=0 + field[01]: type=0x05 (N201-U) val=1520 + field[02]: type=0x06 (N201-I) val=1520 + field[03]: type=0x0b (L3-Params) data[] (len=6): 112233445566 +test_xid_dec_enc(): encoding decoded testData[3] + osmo_gprs_llc_xid_encode() returns 0 + osmo_gprs_llc_xid_encode(): 01001605f01a05f0ac18112233445566 + memcmp() returns 0 +test_xid_dec_enc(): decoding testData[4] = 01001601f41a02081e00002200002908 + osmo_gprs_llc_xid_decode() returns 6 + field[00]: type=0x00 (LLC-Version) val=0 + field[01]: type=0x05 (N201-U) val=500 + field[02]: type=0x06 (N201-I) val=520 + field[03]: type=0x07 (mD) val=0 + field[04]: type=0x08 (mU) val=0 + field[05]: type=0x0a (kU) val=8 +test_xid_dec_enc(): encoding decoded testData[4] + osmo_gprs_llc_xid_encode() returns 0 + osmo_gprs_llc_xid_encode(): 01001601f41a02081e00002200002908 + memcmp() returns 0 + +test_xid_enc_dec(): encoding hand-crafted testData + osmo_gprs_llc_xid_encode() returns 0 + osmo_gprs_llc_xid_encode(): 01000e0fff110a8810424242428410deadbeefac1811223344556630 +test_xid_enc_dec(): decoding encoded hand-crafted testData + osmo_gprs_llc_xid_decode() returns 7 + field[00]: type=0x00 (LLC-Version) val=0 + field[01]: type=0x03 (T200) val=4095 + field[02]: type=0x04 (N200) val=10 + field[03]: type=0x02 (IOV-I) val=1111638594 + field[04]: type=0x01 (IOV-UI) val=3735928559 + field[05]: type=0x0b (L3-Params) data[] (len=6): 112233445566 + field[06]: type=0x0c (Reset) val=0 +