From a536fc644bc6f8ced40f7d40dbbc32f34c295721 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 10 Aug 2016 12:14:57 +0200 Subject: [PATCH] Adding LLC-XID encoder / decoder and unit test The lle-xid encoder/decoder is needed to encode and decode llc xid parameter messages. We need this to exchange sndcp-parameters (SNDCP-XID) and also simple parameters such as encryption IOVs Change-Id: Ia06e4cb08bf9b48c2a4682606d1b1a91d19a9d37 --- openbsc/.gitignore | 1 + openbsc/configure.ac | 1 + openbsc/include/openbsc/Makefile.am | 2 +- openbsc/include/openbsc/gprs_llc_xid.h | 57 ++++++ openbsc/src/gprs/Makefile.am | 2 +- openbsc/src/gprs/gprs_llc_xid.c | 262 +++++++++++++++++++++++++ openbsc/tests/Makefile.am | 2 +- openbsc/tests/testsuite.at | 5 + openbsc/tests/xid/Makefile.am | 21 ++ openbsc/tests/xid/xid_test.c | 164 ++++++++++++++++ openbsc/tests/xid/xid_test.ok | 12 ++ 11 files changed, 526 insertions(+), 3 deletions(-) create mode 100644 openbsc/include/openbsc/gprs_llc_xid.h create mode 100644 openbsc/src/gprs/gprs_llc_xid.c create mode 100644 openbsc/tests/xid/Makefile.am create mode 100644 openbsc/tests/xid/xid_test.c create mode 100644 openbsc/tests/xid/xid_test.ok diff --git a/openbsc/.gitignore b/openbsc/.gitignore index 0a2965d2c..518a9603f 100644 --- a/openbsc/.gitignore +++ b/openbsc/.gitignore @@ -81,6 +81,7 @@ tests/subscr/subscr_test tests/oap/oap_test tests/gtphub/gtphub_test tests/mm_auth/mm_auth_test +tests/xid/xid_test tests/atconfig tests/atlocal diff --git a/openbsc/configure.ac b/openbsc/configure.ac index 09804135f..fbf2930d6 100644 --- a/openbsc/configure.ac +++ b/openbsc/configure.ac @@ -229,6 +229,7 @@ AC_OUTPUT( tests/oap/Makefile tests/gtphub/Makefile tests/mm_auth/Makefile + tests/xid/Makefile doc/Makefile doc/examples/Makefile Makefile) diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am index c272b14de..11f650d12 100644 --- a/openbsc/include/openbsc/Makefile.am +++ b/openbsc/include/openbsc/Makefile.am @@ -18,7 +18,7 @@ noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h gsm_data.h \ gprs_gb_parse.h smpp.h meas_feed.h \ gprs_gsup_client.h bsc_msg_filter.h \ oap.h oap_messages.h \ - gtphub.h + gtphub.h gprs_llc_xid.h openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h openbscdir = $(includedir)/openbsc diff --git a/openbsc/include/openbsc/gprs_llc_xid.h b/openbsc/include/openbsc/gprs_llc_xid.h new file mode 100644 index 000000000..d340d40b7 --- /dev/null +++ b/openbsc/include/openbsc/gprs_llc_xid.h @@ -0,0 +1,57 @@ +/* GPRS LLC XID field encoding/decoding as per 3GPP TS 44.064 */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +/* 3GPP TS 44.064 6.4.1.6 Exchange Identification (XID) + command/response parameter field */ +struct gprs_llc_xid_field { + struct llist_head list; + uint8_t type; /* See also Table 6: LLC layer parameter + negotiation */ + uint8_t *data; /* Payload data (memory is owned by the + * creator of the struct) */ + unsigned int data_len; /* Payload length */ +}; + +/* Transform a list with XID fields into a XID message (dst) */ +int gprs_llc_compile_xid(uint8_t *dst, int dst_maxlen, + const struct llist_head *xid_fields); + +/* Transform a XID message (dst) into a list of XID fields */ +struct llist_head *gprs_llc_parse_xid(const void *ctx, const uint8_t *src, + int src_len); + +/* Create a duplicate of an XID-Field */ +struct gprs_llc_xid_field *gprs_llc_dup_xid_field(const void *ctx, + const struct gprs_llc_xid_field *xid_field); + +/* Copy an llist with xid fields */ +struct llist_head *gprs_llc_copy_xid(const void *ctx, + const struct llist_head *xid_fields); + +/* Dump a list with XID fields (Debug) */ +void gprs_llc_dump_xid_fields(const struct llist_head *xid_fields, + unsigned int logl); + diff --git a/openbsc/src/gprs/Makefile.am b/openbsc/src/gprs/Makefile.am index 6dc7e1634..8cbdd91d2 100644 --- a/openbsc/src/gprs/Makefile.am +++ b/openbsc/src/gprs/Makefile.am @@ -28,7 +28,7 @@ osmo_sgsn_SOURCES = gprs_gmm.c gprs_sgsn.c gprs_sndcp.c gprs_sndcp_vty.c \ sgsn_ctrl.c sgsn_auth.c gprs_subscriber.c \ gprs_utils.c gprs_gsup_client.c \ sgsn_cdr.c sgsn_ares.c \ - oap.c oap_messages.c + oap.c oap_messages.c gprs_llc_xid.c osmo_sgsn_LDADD = \ $(top_builddir)/src/libcommon/libcommon.a \ -lgtp $(OSMO_LIBS) $(LIBOSMOABIS_LIBS) $(LIBCARES_LIBS) \ diff --git a/openbsc/src/gprs/gprs_llc_xid.c b/openbsc/src/gprs/gprs_llc_xid.c new file mode 100644 index 000000000..4b1685ebe --- /dev/null +++ b/openbsc/src/gprs/gprs_llc_xid.c @@ -0,0 +1,262 @@ +/* GPRS LLC XID field encoding/decoding as per 3GPP TS 44.064 */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Parse XID parameter field */ +static int decode_xid_field(struct gprs_llc_xid_field *xid_field, + const uint8_t *src, uint8_t src_len) +{ + uint8_t xl; + uint8_t type; + uint8_t len; + int src_counter = 0; + + /* Exit immediately if it is clear that no + * parseable data is present */ + if (src_len < 1 || !src) + return -EINVAL; + + /* Extract header info */ + xl = (*src >> 7) & 1; + type = (*src >> 2) & 0x1F; + + /* Extract length field */ + len = (*src) & 0x3; + src++; + src_counter++; + if (xl) { + if (src_len < 2) + return -EINVAL; + len = (len << 6) & 0xC0; + len |= ((*src) >> 2) & 0x3F; + src++; + src_counter++; + } + + /* Fill out struct */ + xid_field->type = type; + xid_field->data_len = len; + if (len > 0) { + if (src_len < src_counter + len) + return -EINVAL; + xid_field->data = + talloc_memdup(xid_field,src,xid_field->data_len); + } else + xid_field->data = NULL; + + /* Return consumed length */ + return src_counter + len; +} + +/* Encode XID parameter field */ +static int encode_xid_field(uint8_t *dst, int dst_maxlen, + const struct gprs_llc_xid_field *xid_field) +{ + int xl = 0; + + /* When the length does not fit into 2 bits, + * we need extended length fields */ + if (xid_field->data_len > 3) + xl = 1; + + /* Exit immediately if it is clear that no + * encoding result can be stored */ + if (dst_maxlen < xid_field->data_len + 1 + xl) + return -EINVAL; + + /* There are only 5 bits reserved for the type, exit on exceed */ + if (xid_field->type > 31) + return -EINVAL; + + /* Encode header */ + memset(dst, 0, dst_maxlen); + if (xl) + dst[0] |= 0x80; + dst[0] |= (((xid_field->type) & 0x1F) << 2); + + if (xl) { + dst[0] |= (((xid_field->data_len) >> 6) & 0x03); + dst[1] = ((xid_field->data_len) << 2) & 0xFC; + } else + dst[0] |= ((xid_field->data_len) & 0x03); + + /* Append payload data */ + if (xid_field->data && xid_field->data_len) + memcpy(dst + 1 + xl, xid_field->data, xid_field->data_len); + + /* Return generated length */ + return xid_field->data_len + 1 + xl; +} + +/* Transform a list with XID fields into a XID message (dst) */ +int gprs_llc_compile_xid(uint8_t *dst, int dst_maxlen, + const struct llist_head *xid_fields) +{ + struct gprs_llc_xid_field *xid_field; + int rc; + int byte_counter = 0; + + OSMO_ASSERT(xid_fields); + OSMO_ASSERT(dst); + + llist_for_each_entry_reverse(xid_field, xid_fields, list) { + /* Encode XID-Field */ + rc = encode_xid_field(dst, dst_maxlen, xid_field); + if (rc < 0) + return -EINVAL; + + /* Advance pointer and lower maxlen for the + * next encoding round */ + dst += rc; + byte_counter += rc; + dst_maxlen -= rc; + } + + /* Return generated length */ + return byte_counter; +} + +/* Transform a XID message (dst) into a list of XID fields */ +struct llist_head *gprs_llc_parse_xid(const void *ctx, const uint8_t *src, + int src_len) +{ + struct gprs_llc_xid_field *xid_field; + struct llist_head *xid_fields; + + int rc; + int max_loops = src_len; + + OSMO_ASSERT(src); + + xid_fields = talloc_zero(ctx, struct llist_head); + INIT_LLIST_HEAD(xid_fields); + + while (1) { + /* Bail in case decode_xid_field() constantly returns zero */ + if (max_loops <= 0) { + talloc_free(xid_fields); + return NULL; + } + + /* Decode XID field */ + xid_field = talloc_zero(xid_fields, struct gprs_llc_xid_field); + rc = decode_xid_field(xid_field, src, src_len); + + /* Immediately stop on error */ + if (rc < 0) { + talloc_free(xid_fields); + return NULL; + } + + /* Add parsed XID field to list */ + llist_add(&xid_field->list, xid_fields); + + /* Advance pointer and lower dst_len for the next + * decoding round */ + src += rc; + src_len -= rc; + + /* We are (scuccessfully) done when no further byes are left */ + if (src_len == 0) + return xid_fields; + + max_loops--; + } +} + +/* Create a duplicate of an XID-Field */ +struct gprs_llc_xid_field *gprs_llc_dup_xid_field(const void *ctx, const struct + gprs_llc_xid_field + *xid_field) +{ + struct gprs_llc_xid_field *dup; + + OSMO_ASSERT(xid_field); + + /* Create a copy of the XID field in memory */ + dup = talloc_memdup(ctx, xid_field, sizeof(*xid_field)); + dup->data = talloc_memdup(ctx, xid_field->data, xid_field->data_len); + + /* Unlink duplicate from source list */ + INIT_LLIST_HEAD(&dup->list); + + return dup; +} + +/* Copy an llist with xid fields */ +struct llist_head *gprs_llc_copy_xid(const void *ctx, + const struct llist_head *xid_fields) +{ + struct gprs_llc_xid_field *xid_field; + struct llist_head *xid_fields_copy; + + OSMO_ASSERT(xid_fields); + + xid_fields_copy = talloc_zero(ctx, struct llist_head); + INIT_LLIST_HEAD(xid_fields_copy); + + /* Create duplicates and add them to the target list */ + llist_for_each_entry(xid_field, xid_fields, list) { + llist_add(&gprs_llc_dup_xid_field(ctx, xid_field)->list, + xid_fields_copy); + } + + return xid_fields_copy; +} + +/* Dump a list with XID fields (Debug) */ +void gprs_llc_dump_xid_fields(const struct llist_head *xid_fields, + unsigned int logl) +{ + struct gprs_llc_xid_field *xid_field; + + OSMO_ASSERT(xid_fields); + + llist_for_each_entry(xid_field, xid_fields, list) { + if (xid_field->data_len) { + OSMO_ASSERT(xid_field->data); + LOGP(DLLC, logl, + "XID: type=%d, data_len=%d, data=%s\n", + xid_field->type, xid_field->data_len, + osmo_hexdump_nospc(xid_field->data, + xid_field->data_len)); + } else { + LOGP(DLLC, logl, + "XID: type=%d, data_len=%d, data=NULL\n", + xid_field->type, xid_field->data_len); + } + } +} diff --git a/openbsc/tests/Makefile.am b/openbsc/tests/Makefile.am index 09298a35c..ba5ca28d5 100644 --- a/openbsc/tests/Makefile.am +++ b/openbsc/tests/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = gsm0408 db channel mgcp gprs abis gbproxy trau subscr mm_auth +SUBDIRS = gsm0408 db channel mgcp gprs abis gbproxy trau subscr mm_auth xid if BUILD_NAT SUBDIRS += bsc-nat bsc-nat-trie diff --git a/openbsc/tests/testsuite.at b/openbsc/tests/testsuite.at index dab956888..6470ab9aa 100644 --- a/openbsc/tests/testsuite.at +++ b/openbsc/tests/testsuite.at @@ -124,3 +124,8 @@ cat $abs_srcdir/mm_auth/mm_auth_test.ok > expout AT_CHECK([$abs_top_builddir/tests/mm_auth/mm_auth_test], [], [expout], [ignore]) AT_CLEANUP +AT_SETUP([xid]) +AT_KEYWORDS([xid]) +cat $abs_srcdir/xid/xid_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/xid/xid_test], [], [expout], [ignore]) +AT_CLEANUP diff --git a/openbsc/tests/xid/Makefile.am b/openbsc/tests/xid/Makefile.am new file mode 100644 index 000000000..9b6496594 --- /dev/null +++ b/openbsc/tests/xid/Makefile.am @@ -0,0 +1,21 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBCARES_CFLAGS) + +EXTRA_DIST = xid_test.ok + +noinst_PROGRAMS = xid_test + +xid_test_SOURCES = xid_test.c + +xid_test_LDADD = \ + $(top_builddir)/src/gprs/gprs_llc_xid.o \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOGB_LIBS) \ + $(LIBCARES_LIBS) \ + $(LIBCRYPTO_LIBS) \ + -lgtp -lrt -lm + + diff --git a/openbsc/tests/xid/xid_test.c b/openbsc/tests/xid/xid_test.c new file mode 100644 index 000000000..b77a4ae85 --- /dev/null +++ b/openbsc/tests/xid/xid_test.c @@ -0,0 +1,164 @@ +/* Test LLC-XID Encoding/Decoding */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include + +#include + +#include +#include + +/* Test XID encoding */ +static void test_xid_encode(const void *ctx) +{ + struct gprs_llc_xid_field xid_field_1; + struct gprs_llc_xid_field xid_field_2; + struct gprs_llc_xid_field xid_field_3; + struct gprs_llc_xid_field xid_field_4; + LLIST_HEAD(xid_fields); + uint8_t xid[255]; + uint8_t xid_expected[] = + { 0x10, 0x8c, 0x14, 0x43, 0x43, 0x43, 0x43, 0x43, 0x0b, 0x42, 0x42, + 0x42, 0x05, 0x41 }; + int rc; + + printf("Testing LLC XID-Encoder\n"); + + /* Setup some simple XID data */ + xid_field_1.type = 1; + xid_field_2.type = 2; + xid_field_3.type = 3; + xid_field_4.type = 4; + + xid_field_1.data = (uint8_t *) "A"; + xid_field_2.data = (uint8_t *) "BBB"; + xid_field_3.data = (uint8_t *) "CCCCC"; + xid_field_4.data = NULL; + + xid_field_1.data_len = 1; + xid_field_2.data_len = 3; + xid_field_3.data_len = 5; + xid_field_4.data_len = 0; + + llist_add(&xid_field_4.list, &xid_fields); + llist_add(&xid_field_3.list, &xid_fields); + llist_add(&xid_field_2.list, &xid_fields); + llist_add(&xid_field_1.list, &xid_fields); + + printf("Data to encode:\n"); + gprs_llc_dump_xid_fields(&xid_fields, DSNDCP); + + /* Encode data */ + rc = gprs_llc_compile_xid(xid, sizeof(xid), &xid_fields); + OSMO_ASSERT(rc == 14); + printf("Encoded: %s (%i bytes)\n", osmo_hexdump_nospc(xid, rc), rc); + printf("Expected: %s (%i bytes)\n", + osmo_hexdump_nospc(xid_expected, sizeof(xid_expected)), + (int)sizeof(xid_expected)); + + OSMO_ASSERT(memcmp(xid_expected, xid, sizeof(xid_expected)) == 0); + + printf("\n"); +} + +/* Test XID decoding */ +static void test_xid_decode(const void *ctx) +{ + struct llist_head *xid_fields; + int rc; + + printf("Testing LLC XID-Decoder/Encoder\n"); + + /* Example of a real world LLC-XID message */ + uint8_t xid[] = + { 0x01, 0x00, 0x16, 0x05, 0xf0, 0x1a, 0x05, 0xf0, 0xac, 0xd8, 0x00, + 0x01, 0x00, 0x02, 0x31, 0x82, 0x02, 0x27, 0x89, 0xff, 0xe0, 0x00, 0x0f, + 0x00, 0xa8, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x01, 0x02, + 0x00, 0x03, 0x01, 0x03, 0x00, 0x04, 0x01, 0x04, 0x00, 0x05, 0x01, 0x05, + 0x00, 0x06, 0x00, 0x07, 0x01, 0x07, 0x00, 0x08, 0x01, 0x08, 0x80, 0x00, + 0x04, 0x12, 0x00, 0x40, 0x07 }; + + uint8_t xid_r[512]; + + /* Decode and display XID fields */ + xid_fields = gprs_llc_parse_xid(ctx, xid, sizeof(xid)); + OSMO_ASSERT(xid_fields); + + printf("Decoded:\n"); + gprs_llc_dump_xid_fields(xid_fields, DSNDCP); + + + /* Encode xid-fields again */ + rc = gprs_llc_compile_xid(xid_r, sizeof(xid_r), xid_fields); + printf("Result length=%i\n",rc); + printf("Encoded: %s\n", osmo_hexdump_nospc(xid, sizeof(xid))); + printf("Rencoded: %s\n", osmo_hexdump_nospc(xid_r, rc)); + + OSMO_ASSERT(rc == 64); + OSMO_ASSERT(memcmp(xid, xid_r, sizeof(xid)) == 0); + + /* Free xid fields */ + talloc_free(xid_fields); + + printf("\n"); +} + +static struct log_info_cat gprs_categories[] = { + [DSNDCP] = { + .name = "DSNDCP", + .description = + "GPRS Sub-Network Dependent Control Protocol (SNDCP)", + .enabled = 1,.loglevel = LOGL_DEBUG, + } +}; + +static struct log_info info = { + .cat = gprs_categories, + .num_cat = ARRAY_SIZE(gprs_categories), +}; + +int main(int argc, char **argv) +{ + void *xid_ctx; + + osmo_init_logging(&info); + + xid_ctx = talloc_named_const(NULL, 0, "xid_ctx"); + + test_xid_decode(xid_ctx); + test_xid_encode(xid_ctx); + printf("Done\n"); + + talloc_report_full(xid_ctx, stderr); + OSMO_ASSERT(talloc_total_blocks(xid_ctx) == 1); + return 0; +} + +/* stubs */ +struct osmo_prim_hdr; +int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + abort(); +} diff --git a/openbsc/tests/xid/xid_test.ok b/openbsc/tests/xid/xid_test.ok new file mode 100644 index 000000000..4cf825ca5 --- /dev/null +++ b/openbsc/tests/xid/xid_test.ok @@ -0,0 +1,12 @@ +Testing LLC XID-Decoder/Encoder +Decoded: +Result length=64 +Encoded: 01001605f01a05f0acd8000100023182022789ffe0000f00a8000000010101000201020003010300040104000501050006000701070008010880000412004007 +Rencoded: 01001605f01a05f0acd8000100023182022789ffe0000f00a8000000010101000201020003010300040104000501050006000701070008010880000412004007 + +Testing LLC XID-Encoder +Data to encode: +Encoded: 108c1443434343430b4242420541 (14 bytes) +Expected: 108c1443434343430b4242420541 (14 bytes) + +Done