libosmo-gprs/src/llc/llc_xid.c

308 lines
8.5 KiB
C

/* GPRS LLC protocol implementation as per 3GPP TS 44.064 */
/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
* (C) 2022 by Sysmocom s.f.m.c. GmbH
*
* All Rights Reserved
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#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>
#include <osmocom/gprs/llc/llc_private.h>
const struct value_string gprs_llc_xid_type_names[] = {
{ OSMO_GPRS_LLC_XID_T_VERSION, "LLC-Version" },
{ OSMO_GPRS_LLC_XID_T_IOV_UI, "IOV-UI" },
{ OSMO_GPRS_LLC_XID_T_IOV_I, "IOV-I" },
{ OSMO_GPRS_LLC_XID_T_T200, "T200" },
{ OSMO_GPRS_LLC_XID_T_N200, "N200" },
{ OSMO_GPRS_LLC_XID_T_N201_U, "N201-U" },
{ OSMO_GPRS_LLC_XID_T_N201_I, "N201-I" },
{ OSMO_GPRS_LLC_XID_T_mD, "mD" },
{ OSMO_GPRS_LLC_XID_T_mU, "mU" },
{ OSMO_GPRS_LLC_XID_T_kD, "kD" },
{ OSMO_GPRS_LLC_XID_T_kU, "kU" },
{ OSMO_GPRS_LLC_XID_T_L3_PAR, "L3-Params" },
{ OSMO_GPRS_LLC_XID_T_RESET, "Reset" },
{ OSMO_GPRS_LLC_XID_T_IIOV_UI, "i-IOV-UI" },
{ OSMO_GPRS_LLC_XID_T_IIOV_UI_CNT, "i-IOV-UI-cnt" },
{ 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 gprs_llc_xid_field_is_valid(const struct gprs_llc_xid_field *field)
{
if (field->type >= ARRAY_SIZE(gprs_llc_xid_desc)) {
LOGLLC(LOGL_ERROR,
"Unknown XID field type 0x%02x\n", field->type);
return false;
}
if (gprs_llc_xid_desc[field->type].var_len)
return true;
if (field->var.val_len > 0) {
LOGLLC(LOGL_ERROR,
"XID field %s unexpected var.val_len %u > 0, check your code!\n",
gprs_llc_xid_type_name(field->type),
field->var.val_len);
return false;
}
/* 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) {
LOGLLC(LOGL_ERROR,
"XID field %s value=%u < min=%u\n",
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) {
LOGLLC(LOGL_ERROR,
"XID field %s value=%u > max=%u\n",
gprs_llc_xid_type_name(field->type),
field->val, gprs_llc_xid_desc[field->type].max);
return false;
}
return true;
}
int gprs_llc_xid_encode(uint8_t *data, size_t data_len,
const struct gprs_llc_xid_field *fields,
unsigned int num_fields)
{
uint8_t *wptr = data;
OSMO_ASSERT(data);
for (unsigned int i = 0; i < num_fields; i++) {
const struct gprs_llc_xid_field *field = &fields[i];
uint8_t *hdr, len;
if (!gprs_llc_xid_field_is_valid(field))
return -EINVAL;
/* XID field type */
if (wptr - data >= data_len)
return -EINVAL;
hdr = wptr++;
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 {
if (wptr - data >= data_len)
return -EINVAL;
wptr++;
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) {
if (wptr + len - data > data_len)
return -EINVAL;
memcpy(wptr, field->var.val, len);
wptr += len;
} else {
if (wptr + len - data > data_len)
return -EINVAL;
switch (len) {
case 1:
*wptr = field->val;
wptr++;
break;
case 2:
osmo_store16be(field->val, wptr);
wptr += 2;
break;
case 4:
osmo_store32be(field->val, wptr);
wptr += 4;
break;
default:
/* Shall not happen */
OSMO_ASSERT(0);
}
}
}
return wptr - data;
}
/* returns number of decoded XID fields, negative on error. */
int gprs_llc_xid_decode(struct gprs_llc_xid_field *fields,
unsigned int max_fields,
uint8_t *data, size_t data_len)
{
uint8_t *ptr = &data[0];
unsigned int num_fields = 0;
#define check_len(len, text) \
do { \
if (data_len < (len)) { \
LOGLLC(LOGL_ERROR, "Failed to parse XID: %s\n", text); \
return -EINVAL; \
} \
} while (0)
while (data_len > 0) {
struct gprs_llc_xid_field *field = &fields[num_fields++];
uint8_t len;
if (num_fields > max_fields) {
LOGLLC(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)) {
LOGLLC(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) {
LOGLLC(LOGL_NOTICE,
"XID field %s has unusual length=%u (expected %u)\n",
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:
LOGLLC(LOGL_ERROR,
"Failed to parse XID: unsupported field (%s) length=%u\n",
gprs_llc_xid_type_name(field->type), len);
return -EINVAL;
}
if (!gprs_llc_xid_field_is_valid(field))
return -EINVAL;
}
ptr += len;
}
#undef check_len
return num_fields;
}
/* Return Deep copy of a xid_field array allocated using talloc ctx. */
struct gprs_llc_xid_field *gprs_llc_xid_deepcopy(void *ctx,
const struct gprs_llc_xid_field *src_xid,
size_t src_xid_len)
{
size_t bytes_len = sizeof(*src_xid) * src_xid_len;
struct gprs_llc_xid_field *dst_xid;
unsigned int i;
dst_xid = (struct gprs_llc_xid_field *) talloc_size(ctx, bytes_len);
memcpy(dst_xid, src_xid, bytes_len);
for (i = 0; i < src_xid_len; i++) {
uint8_t *val;
if (dst_xid[i].var.val_len == 0 || dst_xid[i].var.val == NULL)
continue;
val = talloc_size(dst_xid, dst_xid[i].var.val_len);
memcpy(val, dst_xid[i].var.val, dst_xid[i].var.val_len);
dst_xid[i].var.val = val;
}
return dst_xid;
}