aboutsummaryrefslogtreecommitdiffstats
path: root/src/gsm/gsm0808_utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gsm/gsm0808_utils.c')
-rw-r--r--src/gsm/gsm0808_utils.c890
1 files changed, 733 insertions, 157 deletions
diff --git a/src/gsm/gsm0808_utils.c b/src/gsm/gsm0808_utils.c
index 6bf771f8..c9f26d35 100644
--- a/src/gsm/gsm0808_utils.c
+++ b/src/gsm/gsm0808_utils.c
@@ -78,7 +78,7 @@ uint8_t gsm0808_enc_cause(struct msgb *msg, uint16_t cause)
/*! Encode TS 08.08 AoIP transport address IE
* \param[out] msg Message Buffer to which to append IE
* \param[in] ss Socket Address to be used in IE
- * \returns number of bytes added to \a msg */
+ * \returns number of bytes added to \a msg; 0 if msg is too small */
uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
const struct sockaddr_storage *ss)
{
@@ -87,16 +87,25 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
struct sockaddr_in6 *sin6;
uint16_t port = 0;
uint8_t *ptr;
- uint8_t *old_tail;
- uint8_t *tlv_len;
+ const uint8_t len_tl = 2;
+ uint8_t len_v = sizeof(port);
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ss);
OSMO_ASSERT(ss->ss_family == AF_INET || ss->ss_family == AF_INET6);
+ switch (ss->ss_family) {
+ case AF_INET:
+ len_v += IP_V4_ADDR_LEN;
+ break;
+ case AF_INET6:
+ len_v += IP_V6_ADDR_LEN;
+ break;
+ }
+
+ if (msgb_tailroom(msg) < len_tl + len_v)
+ return 0;
+
msgb_put_u8(msg, GSM0808_IE_AOIP_TRASP_ADDR);
- tlv_len = msgb_put(msg,1);
- old_tail = msg->tail;
+ msgb_put_u8(msg, len_v);
switch (ss->ss_family) {
case AF_INET:
@@ -114,9 +123,7 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
}
msgb_put_u16(msg, port);
-
- *tlv_len = (uint8_t) (msg->tail - old_tail);
- return *tlv_len + 2;
+ return len_tl + len_v;
}
/*! Decode TS 08.08 AoIP transport address IE
@@ -132,7 +139,6 @@ int gsm0808_dec_aoip_trasp_addr(struct sockaddr_storage *ss,
struct sockaddr_in6 sin6;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ss);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -180,7 +186,6 @@ int gsm0808_dec_aoip_trasp_addr(struct sockaddr_storage *ss,
* \returns number of bytes parsed */
int gsm0808_dec_osmux_cid(uint8_t *cid, const uint8_t *elem, uint8_t len)
{
- OSMO_ASSERT(cid);
if (!elem)
return -EINVAL;
if (len != 1)
@@ -204,21 +209,21 @@ static void decode_lai(const uint8_t *data, struct osmo_location_area_id *decode
gsm48_decode_lai2(&lai, decoded);
}
-/* Helper function for gsm0808_enc_speech_codec()
- * and gsm0808_enc_speech_codec_list() */
-static uint8_t enc_speech_codec(struct msgb *msg,
- const struct gsm0808_speech_codec *sc)
+/* Helper function for gsm0808_enc_speech_codec[_list]().
+ * Returns number of bytes appended; negative on error. */
+static int enc_speech_codec(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
{
/* See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
uint8_t header = 0;
uint8_t *old_tail;
- bool type_extended = false;
+ bool type_extended;
/* Note: Extended codec types are codec types that require 8 instead
* of 4 bit to fully specify the selected codec. In the following,
* we check if we work with an extended type or not. We also check
* if the codec type is valid at all. */
- switch(sc->type) {
+ switch (sc->type) {
case GSM0808_SCT_FR1:
case GSM0808_SCT_FR2:
case GSM0808_SCT_FR3:
@@ -235,8 +240,7 @@ static uint8_t enc_speech_codec(struct msgb *msg,
break;
default:
/* Invalid codec type specified */
- OSMO_ASSERT(false);
- break;
+ return -EINVAL;
}
old_tail = msg->tail;
@@ -251,11 +255,10 @@ static uint8_t enc_speech_codec(struct msgb *msg,
header |= (1 << 4);
if (type_extended) {
- header |= 0x0f;
+ header |= GSM0808_SCT_EXT;
msgb_put_u8(msg, header);
msgb_put_u8(msg, sc->type);
} else {
- OSMO_ASSERT(sc->type < 0x0f);
header |= sc->type;
msgb_put_u8(msg, header);
}
@@ -273,11 +276,13 @@ static uint8_t enc_speech_codec(struct msgb *msg,
case GSM0808_SCT_FR5:
case GSM0808_SCT_HR4:
case GSM0808_SCT_CSD:
- OSMO_ASSERT((sc->cfg & 0xff00) == 0);
+ if (sc->cfg >> 8)
+ return -EINVAL;
msgb_put_u8(msg, (uint8_t) sc->cfg & 0xff);
break;
default:
- OSMO_ASSERT(sc->cfg == 0);
+ if (sc->cfg != 0)
+ return -EINVAL;
break;
}
@@ -287,27 +292,38 @@ static uint8_t enc_speech_codec(struct msgb *msg,
/*! Encode TS 08.08 Speech Codec IE
* \param[out] msg Message Buffer to which IE will be appended
* \param[in] sc Speech Codec to be encoded into IE
- * \returns number of bytes appended to \a msg */
-uint8_t gsm0808_enc_speech_codec(struct msgb *msg,
- const struct gsm0808_speech_codec *sc)
+ * \returns number of bytes appended to \a msg; negative on error */
+int gsm0808_enc_speech_codec2(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
{
/*! See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
- uint8_t *old_tail;
uint8_t *tlv_len;
-
- OSMO_ASSERT(msg);
- OSMO_ASSERT(sc);
+ int rc;
msgb_put_u8(msg, GSM0808_IE_SPEECH_CODEC);
tlv_len = msgb_put(msg, 1);
- old_tail = msg->tail;
- enc_speech_codec(msg, sc);
+ rc = enc_speech_codec(msg, sc);
+ if (rc < 0)
+ return rc;
- *tlv_len = (uint8_t) (msg->tail - old_tail);
+ *tlv_len = rc;
return *tlv_len + 2;
}
+/*! Deprecated: gsm0808_enc_speech_codec2() wrapper for backwards compatibility.
+ * Mimics the old behavior: crash on missing and/or invalid input. */
+uint8_t gsm0808_enc_speech_codec(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
+{
+ int rc;
+
+ rc = gsm0808_enc_speech_codec2(msg, sc);
+ OSMO_ASSERT(rc > 0);
+
+ return rc;
+}
+
/*! Decode TS 08.08 Speech Codec IE
* \param[out] sc Caller-allocated memory for Speech Codec
* \param[in] elem IE value to be decoded
@@ -320,7 +336,6 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
uint8_t header;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(sc);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -332,7 +347,7 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
/* An extended codec type needs at least two fields,
* bail if the input data length is not sufficient. */
- if ((header & 0x0F) == 0x0F && len < 2)
+ if ((header & 0x0F) == GSM0808_SCT_EXT && len < 2)
return -EINVAL;
elem++;
@@ -347,7 +362,7 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
if (header & (1 << 4))
sc->tf = true;
- if ((header & 0x0F) != 0x0F) {
+ if ((header & 0x0F) != GSM0808_SCT_EXT) {
sc->type = (header & 0x0F);
} else {
sc->type = *elem;
@@ -392,35 +407,46 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
/*! Encode TS 08.08 Speech Codec list
* \param[out] msg Message Buffer to which IE is to be appended
* \param[in] scl Speech Codec List to be encoded into IE
- * \returns number of bytes added to \a msg */
-uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg,
- const struct gsm0808_speech_codec_list *scl)
+ * \returns number of bytes added to \a msg; negative on error */
+int gsm0808_enc_speech_codec_list2(struct msgb *msg,
+ const struct gsm0808_speech_codec_list *scl)
{
/*! See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
uint8_t *old_tail;
uint8_t *tlv_len;
unsigned int i;
- uint8_t rc;
unsigned int bytes_used = 0;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(scl);
-
msgb_put_u8(msg, GSM0808_IE_SPEECH_CODEC_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
for (i = 0; i < scl->len; i++) {
- rc = enc_speech_codec(msg, &scl->codec[i]);
- OSMO_ASSERT(rc >= 1);
+ int rc = enc_speech_codec(msg, &scl->codec[i]);
+ if (rc < 1)
+ return rc;
bytes_used += rc;
- OSMO_ASSERT(bytes_used <= 255);
+ if (bytes_used > 0xff)
+ return -EOVERFLOW;
}
*tlv_len = (uint8_t) (msg->tail - old_tail);
return *tlv_len + 2;
}
+/*! Deprecated: gsm0808_enc_speech_codec_list2() wrapper for backwards compatibility.
+ * Mimics the old behavior: crash on missing and/or invalid input. */
+uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg,
+ const struct gsm0808_speech_codec_list *scl)
+{
+ int rc;
+
+ rc = gsm0808_enc_speech_codec_list2(msg, scl);
+ OSMO_ASSERT(rc > 0);
+
+ return rc;
+}
+
/*! Decode TS 08.08 Speech Codec list IE
* \param[out] scl Caller-provided memory to store codec list
* \param[in] elem IE value to be decoded
@@ -435,7 +461,6 @@ int gsm0808_dec_speech_codec_list(struct gsm0808_speech_codec_list *scl,
int rc;
uint8_t decoded = 0;
- OSMO_ASSERT(scl);
if (!elem)
return -EINVAL;
@@ -472,16 +497,8 @@ uint8_t gsm0808_enc_channel_type(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ct);
OSMO_ASSERT(ct->perm_spch_len <= CHANNEL_TYPE_ELEMENT_MAXLEN - 2);
- /* FIXME: Implement encoding support for Data
- * and Speech + CTM Text Telephony */
- if ((ct->ch_indctr & 0x0f) != GSM0808_CHAN_SPEECH
- && (ct->ch_indctr & 0x0f) != GSM0808_CHAN_SIGN)
- OSMO_ASSERT(false);
-
msgb_put_u8(msg, GSM0808_IE_CHANNEL_TYPE);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
@@ -489,12 +506,45 @@ uint8_t gsm0808_enc_channel_type(struct msgb *msg,
msgb_put_u8(msg, ct->ch_indctr & 0x0f);
msgb_put_u8(msg, ct->ch_rate_type);
- for (i = 0; i < ct->perm_spch_len; i++) {
- byte = ct->perm_spch[i];
+ switch (ct->ch_indctr) {
+ case GSM0808_CHAN_DATA:
+ byte = ct->data_rate;
+
+ if (!ct->data_transparent)
+ byte |= 0x40; /* Set T/NT */
+
+ if (ct->data_rate_allowed_is_set) {
+ OSMO_ASSERT(!ct->data_transparent);
+ byte |= 0x80; /* Set ext */
+ msgb_put_u8(msg, byte);
- if (i < ct->perm_spch_len - 1)
- byte |= 0x80;
+ byte = ct->data_rate_allowed;
+ if (ct->data_asym_pref_is_set) {
+ byte |= 0x80; /* Set ext */
+ msgb_put_u8(msg, byte);
+
+ /* Set asymmetry indication, rest is spare */
+ byte = ct->data_asym_pref << 5;
+ }
+ }
msgb_put_u8(msg, byte);
+ break;
+ case GSM0808_CHAN_SPEECH:
+ case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY:
+ for (i = 0; i < ct->perm_spch_len; i++) {
+ byte = ct->perm_spch[i];
+
+ if (i < ct->perm_spch_len - 1)
+ byte |= 0x80;
+ msgb_put_u8(msg, byte);
+ }
+ break;
+ case GSM0808_CHAN_SIGN:
+ /* Octet 5 is spare */
+ msgb_put_u8(msg, 0);
+ break;
+ default:
+ OSMO_ASSERT(false);
}
*tlv_len = (uint8_t) (msg->tail - old_tail);
@@ -514,7 +564,6 @@ int gsm0808_dec_channel_type(struct gsm0808_channel_type *ct,
uint8_t byte;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ct);
if (!elem)
return -EINVAL;
if (len < 3 || len > 11)
@@ -524,17 +573,60 @@ int gsm0808_dec_channel_type(struct gsm0808_channel_type *ct,
ct->ch_indctr = (*elem) & 0x0f;
elem++;
- ct->ch_rate_type = (*elem) & 0x0f;
+ ct->ch_rate_type = *elem;
elem++;
- for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) {
+ switch (ct->ch_indctr) {
+ case GSM0808_CHAN_DATA:
byte = *elem;
elem++;
- ct->perm_spch[i] = byte & 0x7f;
- if ((byte & 0x80) == 0x00)
- break;
+ ct->data_transparent = !(byte & 0x40); /* T/NT */
+ ct->data_rate = byte & 0x3f;
+
+ /* Optional extension for non-transparent service */
+ if (byte & 0x80) {
+ if (ct->data_transparent)
+ return -EINVAL;
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+ byte = *elem;
+ elem++;
+
+ ct->data_rate_allowed_is_set = true;
+ ct->data_rate_allowed = byte & 0x7f;
+
+ /* Optional extension */
+ if (byte & 0x80) {
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+ byte = *elem;
+ elem++;
+
+ ct->data_asym_pref_is_set = true;
+ ct->data_asym_pref = byte & 0x60 >> 5;
+ }
+ }
+ break;
+ case GSM0808_CHAN_SPEECH:
+ case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY:
+ for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) {
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+
+ byte = *elem;
+ elem++;
+ ct->perm_spch[i] = byte & 0x7f;
+ if ((byte & 0x80) == 0x00)
+ break;
+ }
+ ct->perm_spch_len = i + 1;
+ break;
+ case GSM0808_CHAN_SIGN:
+ /* Octet 5 is spare */
+ break;
+ default:
+ return -ENOTSUP;
}
- ct->perm_spch_len = i + 1;
return (int)(elem - old_elem);
}
@@ -703,8 +795,6 @@ uint8_t gsm0808_enc_encrypt_info(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ei);
OSMO_ASSERT(ei->key_len <= ARRAY_SIZE(ei->key));
OSMO_ASSERT(ei->perm_algo_len <= ENCRY_INFO_PERM_ALGO_MAXLEN);
@@ -721,6 +811,8 @@ uint8_t gsm0808_enc_encrypt_info(struct msgb *msg,
}
msgb_put_u8(msg, perm_algo);
+ /* FIXME: 48.008 3.2.2.10 Encryption Information says:
+ * "When present, the key shall be 8 octets long." */
ptr = msgb_put(msg, ei->key_len);
memcpy(ptr, ei->key, ei->key_len);
@@ -741,7 +833,6 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
unsigned int perm_algo_len = 0;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ei);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -760,6 +851,8 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
}
ei->perm_algo_len = perm_algo_len;
+ /* FIXME: 48.008 3.2.2.10 Encryption Information says:
+ * "When present, the key shall be 8 octets long." */
ei->key_len = len - 1;
memcpy(ei->key, elem, ei->key_len);
elem+=ei->key_len;
@@ -767,6 +860,80 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
return (int)(elem - old_elem);
}
+/*! Encode TS 48.008 Kc128 IE.
+ * \param[out] msg Message Buffer to which IE is to be appended.
+ * \param[in] kc128 Pointer to 16 bytes of Kc128 key data.
+ * \returns number of bytes appended to msg */
+int gsm0808_enc_kc128(struct msgb *msg, const uint8_t *kc128)
+{
+ uint8_t *start = msg->tail;
+ msgb_tv_fixed_put(msg, GSM0808_IE_KC_128, 16, kc128);
+ return msg->tail - start;
+}
+
+/*! Decode TS 48.008 Kc128 IE.
+ * \param[out] kc128 Target buffer for received Kc128 key, 16 bytes long.
+ * \param[in] elem IE value to be decoded (without IE discriminator).
+ * \param[in] len Length of elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_kc128(uint8_t *kc128, const uint8_t *elem, uint8_t len)
+{
+ if (len != 16)
+ return -EINVAL;
+ memcpy(kc128, elem, 16);
+ return len;
+}
+
+/* Store individual Cell Identifier information in a CGI, without clearing the remaining ones.
+ * This is useful to supplement one CGI with information from more than one Cell Identifier,
+ * which in turn is useful to match Cell Identifiers of differing kinds to each other.
+ * Before first invocation, clear the *dst struct externally, this function does only write those members
+ * that are present in parameter u.
+ */
+static void cell_id_to_cgi(struct osmo_cell_global_id *dst,
+ enum CELL_IDENT discr, const union gsm0808_cell_id_u *u)
+{
+ switch (discr) {
+ case CELL_IDENT_WHOLE_GLOBAL:
+ *dst = u->global;
+ return;
+
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ dst->lai = u->global_ps.rai.lac;
+ dst->cell_identity = u->global_ps.cell_identity;
+ return;
+
+ case CELL_IDENT_LAC_AND_CI:
+ dst->lai.lac = u->lac_and_ci.lac;
+ dst->cell_identity = u->lac_and_ci.ci;
+ return;
+
+ case CELL_IDENT_CI:
+ dst->cell_identity = u->ci;
+ return;
+
+ case CELL_IDENT_LAI_AND_LAC:
+ dst->lai = u->lai_and_lac;
+ return;
+
+ case CELL_IDENT_LAC:
+ dst->lai.lac = u->lac;
+ return;
+
+ case CELL_IDENT_SAI:
+ dst->lai = u->sai.lai;
+ return;
+
+ case CELL_IDENT_NO_CELL:
+ case CELL_IDENT_BSS:
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
+ /* No values to set. */
+ return;
+ }
+}
+
/* Return the size of the value part of a cell identifier of given type */
int gsm0808_cell_id_size(enum CELL_IDENT discr)
{
@@ -784,6 +951,10 @@ int gsm0808_cell_id_size(enum CELL_IDENT discr)
case CELL_IDENT_BSS:
case CELL_IDENT_NO_CELL:
return 0;
+ case CELL_IDENT_SAI:
+ return 7;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ return 8;
default:
return -EINVAL;
}
@@ -828,6 +999,20 @@ int gsm0808_decode_cell_id_u(union gsm0808_cell_id_u *out, enum CELL_IDENT discr
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+ case CELL_IDENT_SAI:
+ if (len < 7)
+ return -EINVAL;
+ decode_lai(buf, &out->sai.lai);
+ out->sai.sac = osmo_load16be(buf + sizeof(struct gsm48_loc_area_id));
+ break;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ /* 3GPP TS 48.018 sec 11.3.9 "Cell Identifier" */
+ if (len < 8)
+ return -EINVAL;
+ decode_lai(buf, (struct osmo_location_area_id *)&out->global_ps.rai); /* rai contains lai + non-decoded rac */
+ out->global_ps.rai.rac = *(buf + sizeof(struct gsm48_loc_area_id));
+ out->global_ps.cell_identity = osmo_load16be(buf + sizeof(struct gsm48_loc_area_id) + 1);
+ break;
default:
/* Remaining cell identification types are not implemented. */
return -EINVAL;
@@ -869,6 +1054,26 @@ void gsm0808_msgb_put_cell_id_u(struct msgb *msg, enum CELL_IDENT id_discr, cons
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+
+ case CELL_IDENT_SAI: {
+ const struct osmo_service_area_id *id = &u->sai;
+ struct gsm48_loc_area_id lai;
+ gsm48_generate_lai2(&lai, &id->lai);
+ memcpy(msgb_put(msg, sizeof(lai)), &lai, sizeof(lai));
+ msgb_put_u16(msg, id->sac);
+ break;
+ }
+
+ case CELL_IDENT_WHOLE_GLOBAL_PS: {
+ /* 3GPP TS 48.018 sec 11.3.9 "Cell Identifier" */
+ const struct osmo_cell_global_id_ps *id = &u->global_ps;
+ struct gsm48_loc_area_id lai;
+ gsm48_generate_lai2(&lai, &id->rai.lac);
+ memcpy(msgb_put(msg, sizeof(lai)), &lai, sizeof(lai));
+ memcpy(msgb_put(msg, 1), &id->rai.rac, 1);
+ msgb_put_u16(msg, id->cell_identity);
+ break;
+ }
default:
/* Support for other identifier list types is not implemented. */
OSMO_ASSERT(false);
@@ -885,19 +1090,31 @@ uint8_t gsm0808_enc_cell_id_list2(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
unsigned int i;
-
- OSMO_ASSERT(msg);
- OSMO_ASSERT(cil);
+ uint8_t id_discr;
msgb_put_u8(msg, GSM0808_IE_CELL_IDENTIFIER_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
- msgb_put_u8(msg, cil->id_discr & 0x0f);
+ /* CGI-PS is an osmocom-specific type. In here we don't care about the
+ * PS part, only the CS one. */
+ if (cil->id_discr == CELL_IDENT_WHOLE_GLOBAL_PS)
+ id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ else
+ id_discr = cil->id_discr & 0x0f;
+
+ msgb_put_u8(msg, id_discr);
OSMO_ASSERT(cil->id_list_len <= GSM0808_CELL_ID_LIST2_MAXLEN);
- for (i = 0; i < cil->id_list_len; i++)
- gsm0808_msgb_put_cell_id_u(msg, cil->id_discr, &cil->id_list[i]);
+ for (i = 0; i < cil->id_list_len; i++) {
+ if (cil->id_discr == CELL_IDENT_WHOLE_GLOBAL_PS) {
+ union gsm0808_cell_id_u u;
+ cell_id_to_cgi(&u.global, cil->id_discr, &cil->id_list[i]);
+ gsm0808_msgb_put_cell_id_u(msg, CELL_IDENT_WHOLE_GLOBAL, &u);
+ } else {
+ gsm0808_msgb_put_cell_id_u(msg, cil->id_discr, &cil->id_list[i]);
+ }
+ }
*tlv_len = (uint8_t) (msg->tail - old_tail);
return *tlv_len + 2;
@@ -916,9 +1133,6 @@ uint8_t gsm0808_enc_cell_id_list(struct msgb *msg,
uint8_t *tlv_len;
unsigned int i;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(cil);
-
msgb_put_u8(msg, GSM0808_IE_CELL_IDENTIFIER_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
@@ -1057,6 +1271,31 @@ static int parse_cell_id_lac_list(struct gsm0808_cell_id_list2 *cil, const uint8
return i;
}
+static int parse_cell_id_sai_list(struct gsm0808_cell_id_list2 *cil, const uint8_t *data, size_t remain, size_t *consumed)
+{
+ struct osmo_service_area_id *id;
+ uint16_t *sac_be;
+ size_t lai_offset;
+ int i = 0;
+ const size_t elemlen = sizeof(struct gsm48_loc_area_id) + sizeof(*sac_be);
+
+ *consumed = 0;
+ while (remain >= elemlen) {
+ if (i >= GSM0808_CELL_ID_LIST2_MAXLEN)
+ return -ENOSPC;
+ id = &cil->id_list[i].sai;
+ lai_offset = i * elemlen;
+ decode_lai(&data[lai_offset], &id->lai);
+ sac_be = (uint16_t *)(&data[lai_offset + sizeof(struct gsm48_loc_area_id)]);
+ id->sac = osmo_load16be(sac_be);
+ *consumed += elemlen;
+ remain -= elemlen;
+ i++;
+ }
+
+ return i;
+}
+
/*! Decode Cell Identifier List IE
* \param[out] cil Caller-provided memory to store Cell ID list
* \param[in] elem IE value to be decoded
@@ -1069,7 +1308,6 @@ int gsm0808_dec_cell_id_list2(struct gsm0808_cell_id_list2 *cil,
size_t bytes_elem = 0;
int list_len = 0;
- OSMO_ASSERT(cil);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -1101,6 +1339,12 @@ int gsm0808_dec_cell_id_list2(struct gsm0808_cell_id_list2 *cil,
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+ case CELL_IDENT_SAI:
+ list_len = parse_cell_id_sai_list(cil, elem, len, &bytes_elem);
+ break;
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
default:
/* Remaining cell identification types are not implemented. */
return -EINVAL;
@@ -1131,7 +1375,6 @@ int gsm0808_dec_cell_id_list(struct gsm0808_cell_id_list *cil,
const uint8_t *old_elem = elem;
unsigned int item_count = 0;
- OSMO_ASSERT(cil);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -1289,9 +1532,6 @@ uint8_t gsm0808_enc_cell_id(struct msgb *msg, const struct gsm0808_cell_id *ci)
.id_list_len = 1,
};
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ci);
-
ie_tag = msg->tail;
rc = gsm0808_enc_cell_id_list2(msg, &cil);
@@ -1498,7 +1738,7 @@ int gsm48_mr_cfg_from_gsm0808_sc_cfg(struct gsm48_multi_rate_conf *cfg,
/* Rate 5,15k can never be selected (see table) */
cfg->m5_15 = 0;
- if (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20 & 0xff) {
+ if (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20) {
/* Table Table 7.11.3.1.3-2 lists one mode that selects 4
* rates at once (Config-NB-Code = 1). The rates selected
* are known to be compatible between GERAN and UTRAN, since
@@ -1592,6 +1832,10 @@ int gsm0808_cell_id_u_name(char *buf, size_t buflen,
return snprintf(buf, buflen, "%s", osmo_lai_name(&u->lai_and_lac));
case CELL_IDENT_WHOLE_GLOBAL:
return snprintf(buf, buflen, "%s", osmo_cgi_name(&u->global));
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ return snprintf(buf, buflen, "%s", osmo_cgi_ps_name(&u->global_ps));
+ case CELL_IDENT_SAI:
+ return snprintf(buf, buflen, "%s", osmo_sai_name(&u->sai));
default:
/* For CELL_IDENT_BSS and CELL_IDENT_NO_CELL, just print the discriminator.
* Same for kinds we have no string representation of yet. */
@@ -1599,47 +1843,6 @@ int gsm0808_cell_id_u_name(char *buf, size_t buflen,
}
}
-/* Store individual Cell Identifier information in a CGI, without clearing the remaining ones.
- * This is useful to supplement one CGI with information from more than one Cell Identifier,
- * which in turn is useful to match Cell Identifiers of differing kinds to each other.
- * Before first invocation, clear the *dst struct externally, this function does only write those members
- * that are present in parameter u.
- */
-static void cell_id_to_cgi(struct osmo_cell_global_id *dst,
- enum CELL_IDENT discr, const union gsm0808_cell_id_u *u)
-{
- switch (discr) {
- case CELL_IDENT_WHOLE_GLOBAL:
- *dst = u->global;
- return;
-
- case CELL_IDENT_LAC_AND_CI:
- dst->lai.lac = u->lac_and_ci.lac;
- dst->cell_identity = u->lac_and_ci.ci;
- return;
-
- case CELL_IDENT_CI:
- dst->cell_identity = u->ci;
- return;
-
- case CELL_IDENT_LAI_AND_LAC:
- dst->lai = u->lai_and_lac;
- return;
-
- case CELL_IDENT_LAC:
- dst->lai.lac = u->lac;
- return;
-
- case CELL_IDENT_NO_CELL:
- case CELL_IDENT_BSS:
- case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
- case CELL_IDENT_UTRAN_RNC:
- case CELL_IDENT_UTRAN_LAC_RNC:
- /* No values to set. */
- return;
- }
-}
-
/*! Return true if the common information between the two Cell Identifiers match.
* For example, if a LAC+CI is compared to LAC, return true if the LAC are the same.
* Note that CELL_IDENT_NO_CELL will always return false.
@@ -1762,6 +1965,14 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di
cid->id.global = *cgi;
return;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ cid->id.global_ps = (struct osmo_cell_global_id_ps){
+ .rai = {
+ .lac = cgi->lai,
+ },
+ .cell_identity = cgi->cell_identity,
+ };
+ return;
case CELL_IDENT_LAC_AND_CI:
cid->id.lac_and_ci = (struct osmo_lac_and_ci_id){
.lac = cgi->lai.lac,
@@ -1781,6 +1992,12 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di
cid->id.lac = cgi->lai.lac;
return;
+ case CELL_IDENT_SAI:
+ cid->id.sai = (struct osmo_service_area_id){
+ .lai = cgi->lai,
+ };
+ return;
+
case CELL_IDENT_NO_CELL:
case CELL_IDENT_BSS:
case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
@@ -1805,6 +2022,11 @@ int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808
*cgi = cid->id.global;
return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC | OSMO_CGI_PART_CI;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ cgi->lai = cid->id.global_ps.rai.lac;
+ cgi->cell_identity = cid->id.global_ps.cell_identity;
+ return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC | OSMO_CGI_PART_CI;
+
case CELL_IDENT_LAC_AND_CI:
cgi->lai.lac = cid->id.lac_and_ci.lac;
cgi->cell_identity = cid->id.lac_and_ci.ci;
@@ -1822,6 +2044,10 @@ int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808
cgi->lai.lac = cid->id.lac;
return OSMO_CGI_PART_LAC;
+ case CELL_IDENT_SAI:
+ cgi->lai = cid->id.sai.lai;
+ return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC;
+
case CELL_IDENT_NO_CELL:
case CELL_IDENT_BSS:
case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
@@ -1844,28 +2070,17 @@ const struct value_string gsm0808_cell_id_discr_names[] = {
{ CELL_IDENT_UTRAN_PLMN_LAC_RNC, "UTRAN-PLMN-LAC-RNC" },
{ CELL_IDENT_UTRAN_RNC, "UTRAN-RNC" },
{ CELL_IDENT_UTRAN_LAC_RNC, "UTRAN-LAC-RNC" },
+ { CELL_IDENT_SAI, "SAI" },
+ { CELL_IDENT_WHOLE_GLOBAL_PS, "CGI-PS"},
{ 0, NULL }
};
-#define APPEND_THING(func, args...) do { \
- int remain = buflen - (pos - buf); \
- int l = func(pos, remain, ##args); \
- if (l < 0 || l > remain) \
- pos = buf + buflen; \
- else \
- pos += l; \
- if (l > 0) \
- total_len += l; \
- } while(0)
-#define APPEND_STR(fmt, args...) APPEND_THING(snprintf, fmt, ##args)
-#define APPEND_CELL_ID_U(DISCR, U) APPEND_THING(gsm0808_cell_id_u_name, DISCR, U)
-
char *gsm0808_cell_id_name_buf(char *buf, size_t buflen, const struct gsm0808_cell_id *cid)
{
- char *pos = buf;
- int total_len = 0;
- APPEND_STR("%s:", gsm0808_cell_id_discr_name(cid->id_discr));
- APPEND_CELL_ID_U(cid->id_discr, &cid->id);
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+
+ OSMO_STRBUF_PRINTF(sb, "%s:", gsm0808_cell_id_discr_name(cid->id_discr));
+ OSMO_STRBUF_APPEND(sb, gsm0808_cell_id_u_name, cid->id_discr, &cid->id);
return buf;
}
@@ -1912,30 +2127,31 @@ char *gsm0808_cell_id_name_c(const void *ctx, const struct gsm0808_cell_id *cid)
*/
int gsm0808_cell_id_list_name_buf(char *buf, size_t buflen, const struct gsm0808_cell_id_list2 *cil)
{
- char *pos = buf;
- int total_len = 0;
- int i;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
- APPEND_STR("%s[%u]", gsm0808_cell_id_discr_name(cil->id_discr), cil->id_list_len);
+ OSMO_STRBUF_PRINTF(sb, "%s[%u]",
+ gsm0808_cell_id_discr_name(cil->id_discr),
+ cil->id_list_len);
switch (cil->id_discr) {
case CELL_IDENT_BSS:
case CELL_IDENT_NO_CELL:
- return total_len;
+ return sb.chars_needed;
default:
break;
}
- APPEND_STR(":{");
+ OSMO_STRBUF_PRINTF(sb, ":{");
- for (i = 0; i < cil->id_list_len; i++) {
+ for (unsigned int i = 0; i < cil->id_list_len; i++) {
if (i)
- APPEND_STR(", ");
- APPEND_CELL_ID_U(cil->id_discr, &cil->id_list[i]);
+ OSMO_STRBUF_PRINTF(sb, ", ");
+ OSMO_STRBUF_APPEND(sb, gsm0808_cell_id_u_name,
+ cil->id_discr, &cil->id_list[i]);
}
- APPEND_STR("}");
- return total_len;
+ OSMO_STRBUF_PRINTF(sb, "}");
+ return sb.chars_needed;
}
/*! Return a human-readable representation of \a cil in a static buffer.
@@ -1957,9 +2173,6 @@ char *gsm0808_cell_id_list_name_c(const void *ctx, const struct gsm0808_cell_id_
return buf;
}
-#undef APPEND_STR
-#undef APPEND_CELL_ID_U
-
char *gsm0808_channel_type_name_buf(char *buf, size_t buf_len, const struct gsm0808_channel_type *ct)
{
snprintf(buf, buf_len, "ch_indctr=0x%x ch_rate_type=0x%x perm_spch=%s",
@@ -1982,4 +2195,367 @@ char *gsm0808_channel_type_name_c(const void *ctx, const struct gsm0808_channel_
return gsm0808_channel_type_name_buf(buf, 128, ct);
}
+/*! Encode Group Call Reference IE (3GPP TS 48.008 3.2.2.55).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] gc Group Call Reference to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_group_callref(struct msgb *msg, const struct gsm0808_group_callref *gc)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(gc);
+
+ msgb_put_u8(msg, GSM0808_IE_GROUP_CALL_REFERENCE);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ memcpy(msgb_put(msg, sizeof(*gc)), gc, sizeof(*gc));
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Group Call Reference IE (3GPP TS 48.008 3.2.2.55).
+ * \param[out] gc Group Call Reference structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_group_callref(struct gsm0808_group_callref *gc, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(gc);
+ OSMO_ASSERT(elem);
+
+ if (len != sizeof(*gc))
+ return -EINVAL;
+
+ memcpy(gc, elem, sizeof(*gc));
+
+ return len;
+}
+
+/*! Encode Priority IE (3GPP TS 48.008 3.2.2.18).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] pri Priority to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_priority(struct msgb *msg, const struct gsm0808_priority *pri)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(pri);
+
+ msgb_put_u8(msg, GSM0808_IE_PRIORITY);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ memcpy(msgb_put(msg, sizeof(*pri)), pri, sizeof(*pri));
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Priority IE (3GPP TS 48.008 3.2.2.18).
+ * \param[out] pri Priority structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_priority(struct gsm0808_priority *pri, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(pri);
+ OSMO_ASSERT(elem);
+
+ if (len != sizeof(*pri))
+ return -EINVAL;
+
+ memcpy(pri, elem, sizeof(*pri));
+
+ return len;
+}
+
+/*! Encode VGCS Feature Flags IE (3GPP TS 48.008 3.2.2.88).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] ff VGCS Feature Flags to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_vgcs_feature_flags(struct msgb *msg, const struct gsm0808_vgcs_feature_flags *ff)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(ff);
+
+ msgb_put_u8(msg, GSM0808_IE_VGCS_FEATURE_FLAGS);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ memcpy(msgb_put(msg, sizeof(*ff)), ff, sizeof(*ff));
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode VGCS Feature Flags IE (3GPP TS 48.008 3.2.2.88).
+ * \param[out] ff VGCS Feature Flags structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_vgcs_feature_flags(struct gsm0808_vgcs_feature_flags *ff, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ff);
+ OSMO_ASSERT(elem);
+
+ if (len != sizeof(*ff))
+ return -EINVAL;
+
+ memcpy(ff, elem, sizeof(*ff));
+
+ return len;
+}
+
+/*! Encode Data Identity IE (3GPP TS 48.008 3.2.2.99).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] di Data Identity to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_data_identity(struct msgb *msg, const struct gsm0808_data_identity *ti)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(ti);
+
+ msgb_put_u8(msg, GSM0808_IE_DATA_IDENTITY);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ memcpy(msgb_put(msg, sizeof(*ti)), ti, sizeof(*ti));
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Data Identity IE (3GPP TS 48.008 3.2.2.99).
+ * \param[out] di Data Identity structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_data_identity(struct gsm0808_data_identity *ti, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ti);
+ OSMO_ASSERT(elem);
+
+ if (len < sizeof(*ti))
+ return -EINVAL;
+
+ memcpy(ti, elem, sizeof(*ti));
+
+ return len;
+}
+
+/*! Encode MSISDN IE (3GPP TS 48.008 3.2.2.101).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] msisdn MSISDN to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_msisdn(struct msgb *msg, const char *msisdn)
+{
+ uint8_t *tlv_len;
+ uint8_t bcd_lv[11];
+ int rc;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(msisdn);
+
+ rc = gsm48_encode_bcd_number(bcd_lv, sizeof(bcd_lv), 0, msisdn);
+ if (rc < 1)
+ return 0;
+
+ msgb_put_u8(msg, GSM0808_IE_MSISDN);
+ tlv_len = msgb_put(msg, rc);
+
+ memcpy(tlv_len, bcd_lv, rc);
+
+ *tlv_len = rc - 1;
+ return *tlv_len + 2;
+}
+
+/*! Decode MSISDN IE (3GPP TS 48.008 3.2.2.101).
+ * \param[out] msisdn MSISDN structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_msisdn(char *msisdn, const char *elem, uint8_t len)
+{
+ OSMO_ASSERT(msisdn);
+ OSMO_ASSERT(elem);
+
+ if (len < 1)
+ return -EINVAL;
+
+ gsm48_decode_bcd_number(msisdn, MSISDN_MAXLEN + 1, (uint8_t *)(elem - 1), 0);
+
+ return len;
+}
+
+/*! Encode Talker Identity IE (3GPP TS 48.008 3.2.2.91).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] ti Talker Identity to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_talker_identity(struct msgb *msg, const struct gsm0808_talker_identity *ti)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+ uint8_t *ptr;
+ unsigned int bytes;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(ti);
+
+ msgb_put_u8(msg, GSM0808_IE_TALKER_IDENTITY);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ bytes = (ti->id_bits + 7) >> 3;
+ ptr = msgb_put(msg, 1 + bytes);
+ ptr[0] = -ti->id_bits & 0x7;
+ memcpy(ptr + 1, ti->talker_id, bytes);
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Talker Identity IE (3GPP TS 48.008 3.2.2.91).
+ * \param[out] ti Talker Identity structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_talker_identity(struct gsm0808_talker_identity *ti, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ti);
+ OSMO_ASSERT(elem);
+
+ if (len < 2)
+ return -EINVAL;
+ unsigned int bytes;
+
+ bytes = len - 1;
+ if (bytes > TALKER_IDENTITY_MAXLEN)
+ return -EINVAL;
+ ti->id_bits = (bytes << 3) - (elem[0] & 0x7);
+ memcpy(ti->talker_id, elem + 1, bytes);
+
+ return len;
+}
+
+/*! Encode Assignment Requirements IE (3GPP TS 48.008 3.2.2.52).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] ar Assignment Requirement to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_assign_req(struct msgb *msg, const enum gsm0808_assignment_requirement ar)
+{
+ uint8_t *ptr;
+
+ OSMO_ASSERT(msg);
+
+ msgb_put_u8(msg, GSM0808_IE_ASSIGNMENT_REQUIREMENT);
+
+ ptr = msgb_put(msg, 1);
+ ptr[0] = ar;
+
+ return 2;
+}
+
+/*! Decode Assignment Requirements IE (3GPP TS 48.008 3.2.2.52).
+ * \param[out] ar Assignment Requirements enum to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_assign_req(enum gsm0808_assignment_requirement *ar, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ar);
+ OSMO_ASSERT(elem);
+
+ if (len != 1)
+ return -EINVAL;
+
+ *ar = elem[0];
+
+ return 1;
+}
+
+/*! Encode Cell Identifier List Segment IE (3GPP TS 48.008 3.2.2.27a).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] ie_type Type of IE to use (5 different lists are specified.)
+ * \param[in] ci Cell Identifier List Segment to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_cell_id_list_segment(struct msgb *msg, uint8_t ie_type,
+ const struct gsm0808_cell_id_list_segment *ci)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+ int rc;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(ci);
+
+ msgb_put_u8(msg, ie_type);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ msgb_put_u8(msg, (ci->seq_last << 4) | ci->seq_number);
+
+ rc = gsm0808_enc_cell_id_list2(msg, &ci->cil);
+ if (rc <= 0)
+ return rc;
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Cell Identifier List Segment IE (3GPP TS 48.008 3.2.2.27a).
+ * \param[out] ci Cell Identifier List Segment structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_cell_id_list_segment(struct gsm0808_cell_id_list_segment *ci, const uint8_t *elem, uint8_t len)
+{
+ int rc;
+
+ OSMO_ASSERT(ci);
+ OSMO_ASSERT(elem);
+
+ if (len < 1)
+ return -EINVAL;
+
+ ci->seq_last = elem[0] >> 4;
+ ci->seq_number = elem[0] & 0x0f;
+
+ rc = gsm0808_dec_cell_id_list2(&ci->cil, elem + 1, len - 1);
+ if (rc < 0)
+ return rc;
+
+ return len;
+}
+
+/*! Decode Call Identifier IE (3GPP TS 48.008 3.2.2.105).
+ * \param[out] ci Call Identifier structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_call_id(uint32_t *ci, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ci);
+ OSMO_ASSERT(elem);
+
+ if (len != 4)
+ return -EINVAL;
+
+ /* The Call Identifier is stored as little endian. */
+ *ci = osmo_load32le(elem);
+
+ return 4;
+}
+
/*! @} */