diff options
Diffstat (limited to 'src/gsm/gsm0808_utils.c')
-rw-r--r-- | src/gsm/gsm0808_utils.c | 890 |
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; +} + /*! @} */ |