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
+
--
To view, visit
https://gerrit.osmocom.org/c/libosmo-gprs/+/29482
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings
Gerrit-Project: libosmo-gprs
Gerrit-Branch: master
Gerrit-Change-Id: I16b3f7ab82c489144098aad0dccc4c67d8382c67
Gerrit-Change-Number: 29482
Gerrit-PatchSet: 5
Gerrit-Owner: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: osmith <osmith(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-MessageType: merged