diff options
author | Neels Hofmeyr <neels@hofmeyr.de> | 2020-05-26 02:45:23 +0200 |
---|---|---|
committer | Neels Hofmeyr <neels@hofmeyr.de> | 2020-06-16 15:17:48 +0200 |
commit | 83025bf1a6a97d2b3931ea3f0bebf63afd8ccd11 (patch) | |
tree | 94c03ffb53088ff9f388c98a44d9c832fa4138eb | |
parent | a13fb750305cad14df5ecc98ee3006965b418cbc (diff) |
add osmo_mobile_identity API
Implement better API around 3GPP TS 24.008 Mobile Identity coding.
struct osmo_mobile_identity is a decoded representation of the raw Mobile
Identity, with a string representation as well as dedicated raw uint32_t TMSI.
The aim is to remove all uncertainty about decoded buffer sizes / data types.
I have patches ready for current osmo CNI programs, replacing the Mobile
Identity coding with this new API. Deprecate the old MI API.
osmo-bsc: I71c3b4c65dbfdfa51409e09d4868aea83225338a
osmo-msc: Ic3f969e739654c1e8c387aedeeba5cce07fe2307
osmo-sgsn: I4cacb10bac419633ca0c14f244f9903f7f517b49
Note that some GPRS and SGs related coding is done here in libosmocore and
hence currently remains using the old implementation (see previous version of
this patch: Ic3f969e739654c1e8c387aedeeba5cce07fe2307).
New API functions provide properly size-checking implementations of:
- decoding a raw MI from a bunch of MI octets;
- locating and decoding MI from a full 3GPP TS 24.008 Complete Layer 3 msgb;
- encoding to a buffer;
- encoding to the end of a msgb.
Other than the old gsm48_generate_mid(), omit a TLV tag and length from
encoding. Many callers manually stripped the tag and value after calling
gsm48_generate_mid(). The aim is to leave writing a TL to the caller entirely,
especially since some callers need to use a TvL, i.e. support a variable-size
length of 8 or 16 bit.
New validity checks so far not implemented anywhere else:
- stricter validation of number of digits of IMSI, IMEI, IMEI-SV MI.
- stricter on filler nibbles to be 0xf.
As a result, applications using osmo_mobile_identity will be stricter in
rejecting coding mistakes (some of which we currently have in our test suites,
and which we'll need to fix).
Rationale:
While implementing osmo-bsc's MSC pooling feature in osmo-bsc, this API will be
used to reduce the number of times a Mobile Identity is extracted from a raw
RSL message.
Extracting the Mobile Identity from messages has numerous duplicate
implementations across our code with various levels of specialization.
https://xkcd.com/927/
To name a few:
- libosmocore: gsm48_mi_to_string(), osmo_mi_name_buf()
- osmo-bsc: extract_sub()
- osmo-msc: mm_rx_loc_upd_req(), cm_serv_reuse_conn(), gsm48_rx_mm_serv_req(),
vlr_proc_acc_req()
We have existing functions to produce a human readable string from a Mobile
Identity, more or less awkward:
- gsm48_mi_to_string() decodes a TMSI as a decimal number. These days we use
hexadecimal TMSI everywhere.
- osmo_mi_name_buf() decodes the BCD digits from a raw MI every time, so we'd
need to pass around the raw message bytes. Also, osmo_mi_name_buf() has the
wrong signature, it should return a length like snprintf().
- osmo-bsc's extract_sub() first uses gsm48_mi_to_string() which encodes the
raw uint32_t TMSI to a string, and then calls strtoul() via
tmsi_from_string() to code those back to a raw uint32_t.
Each of the above implementations employ their own size overflow checks, each
invoke osmo_bcd2str() and implement their own TMSI osmo_load32be() handling.
Too much code dup, let's hope that each and every one is correct.
In osmo-bsc, I am now implementing MSC pooling, and need to extract NRI bits
from a TMSI Mobile Identity. Since none of the above functions are general
enough to be re-used, I found myself again copy-pasting Mobile Identity code:
locating the MI in a 24.008 message with proper size checks, decoding MI
octets.
This time I would like it to become a generally re-usable API.
This patch was first merged as Ic3f969e739654c1e8c387aedeeba5cce07fe2307 and
caused test fallout, because it re-implemented old API with the new stricter
decoding. In this patch version, old API remains 1:1 unchanged to avoid such
fallout. Applications will soon switch to the new osmo_mobile_identity API and
become stricter on MI coding when that happens, not implicitly by a new
libosmocore version.
Change-Id: If4f7be606e54cfa1c59084cf169785b1cbda5cf5
-rw-r--r-- | include/osmocom/core/utils.h | 1 | ||||
-rw-r--r-- | include/osmocom/gsm/gsm48.h | 57 | ||||
-rw-r--r-- | src/gsm/gsm48.c | 466 | ||||
-rw-r--r-- | src/gsm/libosmogsm.map | 8 | ||||
-rw-r--r-- | src/utils.c | 59 | ||||
-rw-r--r-- | tests/gsm0408/gsm0408_test.c | 363 | ||||
-rw-r--r-- | tests/gsm0408/gsm0408_test.ok | 51 | ||||
-rw-r--r-- | tests/utils/utils_test.c | 7 | ||||
-rw-r--r-- | tests/utils/utils_test.ok | 14 |
9 files changed, 1011 insertions, 15 deletions
diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h index e6377867..86191209 100644 --- a/include/osmocom/core/utils.h +++ b/include/osmocom/core/utils.h @@ -55,6 +55,7 @@ char osmo_bcd2char(uint8_t bcd); uint8_t osmo_char2bcd(char c); int osmo_bcd2str(char *dst, size_t dst_size, const uint8_t *bcd, int start_nibble, int end_nibble, bool allow_hex); +int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_nibble, int end_nibble, bool allow_hex); int osmo_hexparse(const char *str, uint8_t *b, int max_len); diff --git a/include/osmocom/gsm/gsm48.h b/include/osmocom/gsm/gsm48.h index 7c68b1d2..f772f4a1 100644 --- a/include/osmocom/gsm/gsm48.h +++ b/include/osmocom/gsm/gsm48.h @@ -4,10 +4,12 @@ #include <stdbool.h> +#include <osmocom/core/defs.h> #include <osmocom/core/msgb.h> #include <osmocom/gsm/tlv.h> #include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_23_003.h> #include <osmocom/gsm/gsm48_ie.h> #include <osmocom/gsm/gsm23003.h> @@ -48,16 +50,55 @@ void gsm48_generate_lai(struct gsm48_loc_area_id *lai48, uint16_t mcc, void gsm48_generate_lai2(struct gsm48_loc_area_id *lai48, const struct osmo_location_area_id *lai); #define GSM48_MID_MAX_SIZE 11 -int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi); -int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi); -uint8_t gsm48_generate_mid(uint8_t *buf, const char *id, uint8_t mi_type); +int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi) + OSMO_DEPRECATED_OUTSIDE("Instead use: l = msgb_tl_put(msg, GSM48_IE_MOBILE_ID);" + " *l = osmo_mobile_identity_encode_msgb(...)"); +int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi) + OSMO_DEPRECATED_OUTSIDE("Instead use: l = msgb_tl_put(msg, GSM48_IE_MOBILE_ID);" + " *l = osmo_mobile_identity_encode_msgb(...)"); +uint8_t gsm48_generate_mid(uint8_t *buf, const char *id, uint8_t mi_type) + OSMO_DEPRECATED_OUTSIDE("Instead use: l = msgb_tl_put(msg, GSM48_IE_MOBILE_ID);" + " *l = osmo_mobile_identity_encode_msgb(...)"); -/* Convert Mobile Identity (10.5.1.4) to string */ -int gsm48_mi_to_string(char *string, int str_len, const uint8_t *mi, int mi_len); const char *gsm48_mi_type_name(uint8_t mi); -const char *osmo_mi_name(const uint8_t *mi, uint8_t mi_len); -char *osmo_mi_name_buf(char *buf, size_t buf_len, const uint8_t *mi, uint8_t mi_len); -char *osmo_mi_name_c(const void *ctx, const uint8_t *mi, uint8_t mi_len); +/* Convert encoded Mobile Identity (10.5.1.4) to string */ +int gsm48_mi_to_string(char *string, int str_len, const uint8_t *mi, int mi_len) + OSMO_DEPRECATED_OUTSIDE("Instead use osmo_mobile_identity_decode()"); +const char *osmo_mi_name(const uint8_t *mi, uint8_t mi_len) + OSMO_DEPRECATED_OUTSIDE("Instead use osmo_mobile_identity_to_str_c()"); +char *osmo_mi_name_buf(char *buf, size_t buf_len, const uint8_t *mi, uint8_t mi_len) + OSMO_DEPRECATED_OUTSIDE("Instead use osmo_mobile_identity_to_str_buf()"); +char *osmo_mi_name_c(const void *ctx, const uint8_t *mi, uint8_t mi_len) + OSMO_DEPRECATED_OUTSIDE("Instead use osmo_mobile_identity_to_str_c()"); + +/*! Decoded representation of a Mobile Identity (3GPP TS 24.008 10.5.1.4). + * See osmo_mobile_identity_decode() and osmo_mobile_identity_from_l3(). */ +struct osmo_mobile_identity { + /*! A GSM_MI_TYPE_* constant (like GSM_MI_TYPE_IMSI). */ + uint8_t type; + /*! Decoded Mobile Identity digits or TMSI value. IMSI, IMEI and IMEISV as digits like + * "12345678", and TMSI is represented as raw uint32_t. */ + union { + /*! type == GSM_MI_TYPE_IMSI. */ + char imsi[GSM23003_IMSI_MAX_DIGITS + 1]; + /*! type == GSM_MI_TYPE_IMEI. */ + char imei[GSM23003_IMEI_NUM_DIGITS + 1]; + /*! type == GSM_MI_TYPE_IMEISV. */ + char imeisv[GSM23003_IMEISV_NUM_DIGITS + 1]; + /*! TMSI / P-TMSI / M-TMSI integer value if type == GSM_MI_TYPE_TMSI. */ + uint32_t tmsi; + }; +}; + +int osmo_mobile_identity_to_str_buf(char *buf, size_t buflen, const struct osmo_mobile_identity *mi); +char *osmo_mobile_identity_to_str_c(void *ctx, const struct osmo_mobile_identity *mi); +int osmo_mobile_identity_cmp(const struct osmo_mobile_identity *a, const struct osmo_mobile_identity *b); +int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *mi_data, uint8_t mi_len, + bool allow_hex); +int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct msgb *msg, bool allow_hex); +int osmo_mobile_identity_encoded_len(const struct osmo_mobile_identity *mi, int *mi_digits); +int osmo_mobile_identity_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_mobile_identity *mi, bool allow_hex); +int osmo_mobile_identity_encode_msgb(struct msgb *msg, const struct osmo_mobile_identity *mi, bool allow_hex); /* Parse Routeing Area Identifier */ void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf); diff --git a/src/gsm/gsm48.c b/src/gsm/gsm48.c index 8d0998bb..4368ce23 100644 --- a/src/gsm/gsm48.c +++ b/src/gsm/gsm48.c @@ -45,6 +45,7 @@ #include <osmocom/gsm/protocol/gsm_04_80.h> #include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/gsm/protocol/gsm_04_08_gprs.h> +#include <osmocom/gsm/protocol/gsm_23_003.h> /*! \addtogroup gsm0408 * @{ @@ -458,7 +459,8 @@ const char *gsm48_mi_type_name(uint8_t mi) return get_value_string(mi_type_names, mi); } -/*! Return a human readable representation of a Mobile Identity in caller-provided buffer. +/*! Deprecated, see osmo_mobile_identity instead. + * Return a human readable representation of a Mobile Identity in caller-provided buffer. * \param[out] buf caller-provided output buffer * \param[in] buf_len size of buf in bytes * \param[in] mi Mobile Identity buffer containing 3GPP TS 04.08 style MI type and data. @@ -497,7 +499,8 @@ char *osmo_mi_name_buf(char *buf, size_t buf_len, const uint8_t *mi, uint8_t mi_ } } -/*! Return a human readable representation of a Mobile Identity in static buffer. +/*! Deprecated, see osmo_mobile_identity instead. + * Return a human readable representation of a Mobile Identity in static buffer. * \param[in] mi Mobile Identity buffer containing 3GPP TS 04.08 style MI type and data. * \param[in] mi_len Length of mi. * \return A string like "IMSI-1234567", "TMSI-0x1234ABCD" or "unknown", "TMSI-invalid"... @@ -508,7 +511,8 @@ const char *osmo_mi_name(const uint8_t *mi, uint8_t mi_len) return osmo_mi_name_buf(mi_name, sizeof(mi_name), mi, mi_len); } -/*! Return a human readable representation of a Mobile Identity in dynamically-allocated buffer. +/*! Deprecated, see osmo_mobile_identity instead. + * Return a human readable representation of a Mobile Identity in dynamically-allocated buffer. * \param[in] ctx talloc context from which to allocate output buffer * \param[in] mi Mobile Identity buffer containing 3GPP TS 04.08 style MI type and data. * \param[in] mi_len Length of mi. @@ -524,6 +528,450 @@ char *osmo_mi_name_c(const void *ctx, const uint8_t *mi, uint8_t mi_len) return osmo_mi_name_buf(mi_name, buf_len, mi, mi_len); } +/*! Extract Mobile Identity from encoded bytes (3GPP TS 24.008 10.5.1.4). + * + * On failure (negative return value), mi->type == GSM_MI_TYPE_NONE, mi->string[] is all-zero and mi->tmsi == + * GSM_RESERVED_TMSI. + * + * On success, mi->type reflects the decoded Mobile Identity type (GSM_MI_TYPE_IMSI, GSM_MI_TYPE_TMSI, GSM_MI_TYPE_IMEI + * or GSM_MI_TYPE_IMEISV). + * + * On success, mi->string always contains a human readable representation of the Mobile Identity digits: IMSI, IMEI and + * IMEISV as digits like "12345678", and TMSI as "0x" and 8 hexadecimal digits like "0x1234abcd". + * + * mi->tmsi contains the uint32_t TMSI value iff the extracted Mobile Identity was a TMSI, or GSM_RESERVED_TMSI + * otherwise. + * + * \param[out] mi Return buffer for decoded Mobile Identity. + * \param[in] mi_data The encoded Mobile Identity octets. + * \param[in] mi_len Number of octets in mi_data. + * \param[in] allow_hex If false, hexadecimal digits (>9) result in an error return value. + * \returns 0 on success, negative on error: -EBADMSG = invalid length indication or invalid data, + * -EINVAL = unknown Mobile Identity type. + */ +int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *mi_data, uint8_t mi_len, + bool allow_hex) +{ + int rc; + int nibbles_len; + char *str; + size_t str_size; + + if (!mi_data || mi_len < 1) + return -EBADMSG; + + nibbles_len = (mi_len - 1) * 2 + ((mi_data[0] & GSM_MI_ODD) ? 1 : 0); + + *mi = (struct osmo_mobile_identity){ + .type = mi_data[0] & GSM_MI_TYPE_MASK, + }; + + /* First do length checks */ + switch (mi->type) { + case GSM_MI_TYPE_TMSI: + mi->tmsi = GSM_RESERVED_TMSI; + if (nibbles_len != (GSM23003_TMSI_NUM_BYTES * 2)) { + rc = -EBADMSG; + goto return_error; + } + break; + + case GSM_MI_TYPE_IMSI: + if (nibbles_len < GSM23003_IMSI_MIN_DIGITS || nibbles_len > GSM23003_IMSI_MAX_DIGITS) { + rc = -EBADMSG; + goto return_error; + } + str = mi->imsi; + str_size = sizeof(mi->imsi); + break; + + case GSM_MI_TYPE_IMEI: + if (nibbles_len != GSM23003_IMEI_NUM_DIGITS && nibbles_len != GSM23003_IMEI_NUM_DIGITS_NO_CHK) { + rc = -EBADMSG; + goto return_error; + } + str = mi->imei; + str_size = sizeof(mi->imei); + break; + + case GSM_MI_TYPE_IMEISV: + if (nibbles_len != GSM23003_IMEISV_NUM_DIGITS) { + rc = -EBADMSG; + goto return_error; + } + str = mi->imeisv; + str_size = sizeof(mi->imeisv); + break; + + default: + rc = -EINVAL; + goto return_error; + } + + /* Decode BCD digits */ + switch (mi->type) { + case GSM_MI_TYPE_TMSI: + /* MI is a 32bit integer TMSI. Length has been checked above. */ + if ((mi_data[0] & 0xf0) != 0xf0) { + /* A TMSI always has the first nibble == 0xf */ + rc = -EBADMSG; + goto return_error; + } + mi->tmsi = osmo_load32be(&mi_data[1]); + return 0; + + case GSM_MI_TYPE_IMSI: + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMEISV: + /* If the length is even, the last nibble (higher nibble of last octet) must be 0xf */ + if (!(mi_data[0] & GSM_MI_ODD) + && ((mi_data[mi_len - 1] & 0xf0) != 0xf0)) { + rc = -EBADMSG; + goto return_error; + } + rc = osmo_bcd2str(str, str_size, mi_data, 1, 1 + nibbles_len, allow_hex); + /* rc checked below */ + break; + + default: + /* Already handled above, but as future bug paranoia: */ + rc = -EINVAL; + goto return_error; + } + + /* check mi->str printing rc */ + if (rc < 1 || rc >= str_size) { + rc = -EBADMSG; + goto return_error; + } + return 0; + +return_error: + *mi = (struct osmo_mobile_identity){ + .type = GSM_MI_TYPE_NONE, + }; + return rc; +} + +/*! Return the number of encoded Mobile Identity octets, without actually encoding. + * Useful to write tag-length header before encoding the MI. + * \param[in] mi Mobile Identity. + * \param[out] mi_digits If not NULL, store the number of nibbles of used MI data (i.e. strlen(mi->string) or 8 for a TMSI). + * \return octets that osmo_mobile_identity_encode_msgb() will write for this mi. + */ +int osmo_mobile_identity_encoded_len(const struct osmo_mobile_identity *mi, int *mi_digits) +{ + int mi_nibbles; + if (!mi) + return -EINVAL; + switch (mi->type) { + case GSM_MI_TYPE_TMSI: + mi_nibbles = GSM23003_TMSI_NUM_BYTES * 2; + break; + case GSM_MI_TYPE_IMSI: + mi_nibbles = strlen(mi->imsi); + if (mi_nibbles < GSM23003_IMSI_MIN_DIGITS + || mi_nibbles > GSM23003_IMSI_MAX_DIGITS) + return -EINVAL; + break; + case GSM_MI_TYPE_IMEI: + mi_nibbles = strlen(mi->imei); + if (mi_nibbles < GSM23003_IMEI_NUM_DIGITS_NO_CHK + || mi_nibbles > GSM23003_IMEI_NUM_DIGITS) + return -EINVAL; + break; + case GSM_MI_TYPE_IMEISV: + mi_nibbles = strlen(mi->imeisv); + if (mi_nibbles != GSM23003_IMEISV_NUM_DIGITS) + return -EINVAL; + break; + default: + return -ENOTSUP; + } + + if (mi_digits) + *mi_digits = mi_nibbles; + + /* one type nibble, plus the MI nibbles, plus a filler nibble to complete the last octet: + * mi_octets = ceil((float)(mi_nibbles + 1) / 2) + */ + return (mi_nibbles + 2) / 2; +} + +/*! Encode Mobile Identity from uint32_t (TMSI) or digits string (all others) (3GPP TS 24.008 10.5.1.4). + * + * \param[out] buf Return buffer for encoded Mobile Identity. + * \param[in] buflen sizeof(buf). + * \param[in] mi Mobile identity to encode. + * \param[in] allow_hex If false, hexadecimal digits (>9) result in an error return value. + * \returns Amount of bytes written to buf, or negative on error. + */ +int osmo_mobile_identity_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_mobile_identity *mi, bool allow_hex) +{ + int rc; + int nibbles_len; + int mi_octets; + const char *mi_str; + + if (!buf || !buflen) + return -EIO; + + mi_octets = osmo_mobile_identity_encoded_len(mi, &nibbles_len); + if (mi_octets < 0) + return mi_octets; + if (mi_octets > buflen) + return -ENOSPC; + + buf[0] = (mi->type & GSM_MI_TYPE_MASK) | ((nibbles_len & 1) ? GSM_MI_ODD : 0); + + switch (mi->type) { + case GSM_MI_TYPE_TMSI: + buf[0] |= 0xf0; + osmo_store32be(mi->tmsi, &buf[1]); + return mi_octets; + + case GSM_MI_TYPE_IMSI: + mi_str = mi->imsi; + break; + case GSM_MI_TYPE_IMEI: + mi_str = mi->imei; + break; + case GSM_MI_TYPE_IMEISV: + mi_str = mi->imeisv; + break; + default: + return -ENOTSUP; + } + rc = osmo_str2bcd(buf, buflen, mi_str, 1, -1, allow_hex); + if (rc != mi_octets) + return -EINVAL; + return mi_octets; +} + +/*! Encode Mobile Identity type and BCD digits, appended to a msgb. + * Example to add a GSM48_IE_MOBILE_ID IEI with tag and length to a msgb: + * + * struct osmo_mobile_identity mi = { .type = GSM_MI_TYPE_IMSI, .tmsi = random_tmsi, }; + * uint8_t *l = msgb_tl_put(msg, GSM48_IE_MOBILE_ID); + * int rc = osmo_mobile_identity_encode_msgb(msg, &mi, false); + * if (rc < 0) + * goto error; + * *l = rc; + * + * Example to add a BSSGP_IE_IMSI with tag and variable-size length, where the + * length needs to be known at the time of writing the IE tag-length header: + * + * struct osmo_mobile_identity mi = { .type = GSM_MI_TYPE_IMSI, }; + * OSMO_STRLCPY_ARRAY(mi.imsi, pinfo->imsi); + * msgb_tvl_put(msg, BSSGP_IE_IMSI, osmo_mobile_identity_encoded_len(&mi, NULL)); + * if (osmo_mobile_identity_encode_msgb(msg, &mi, false) < 0) + * goto error; + */ +int osmo_mobile_identity_encode_msgb(struct msgb *msg, const struct osmo_mobile_identity *mi, bool allow_hex) +{ + int rc = osmo_mobile_identity_encode_buf(msg->tail, msgb_tailroom(msg), mi, allow_hex); + if (rc < 0) + return rc; + msgb_put(msg, rc); + return rc; +} + +/*! Extract Mobile Identity from a Complete Layer 3 message. + * + * Determine the Mobile Identity data and call osmo_mobile_identity_decode() to return a decoded struct + * osmo_mobile_identity. + * + * \param[out] mi Return buffer for decoded Mobile Identity. + * \param[in] msg The Complete Layer 3 message to extract from (LU, CM Service Req or Paging Resp). + * \returns 0 on success, negative on error: return codes as defined in osmo_mobile_identity_decode(), or + * -ENOTSUP = not a Complete Layer 3 message, + */ +int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct msgb *msg, bool allow_hex) +{ + const struct gsm48_hdr *gh; + int8_t pdisc = 0; + uint8_t mtype = 0; + const struct gsm48_loc_upd_req *lu; + const uint8_t *cm2_buf; + uint8_t cm2_len; + const uint8_t *mi_start; + const struct gsm48_pag_resp *paging_response; + const uint8_t *mi_data; + uint8_t mi_len; + const struct gsm48_imsi_detach_ind *idi; + + *mi = (struct osmo_mobile_identity){ + .type = GSM_MI_TYPE_NONE, + .tmsi = GSM_RESERVED_TMSI, + }; + + if (msgb_l3len(msg) < sizeof(*gh)) + return -EBADMSG; + + gh = msgb_l3(msg); + pdisc = gsm48_hdr_pdisc(gh); + mtype = gsm48_hdr_msg_type(gh); + + switch (pdisc) { + case GSM48_PDISC_MM: + + switch (mtype) { + case GSM48_MT_MM_LOC_UPD_REQUEST: + /* First make sure that lu-> can be dereferenced */ + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) + return -EBADMSG; + + /* Now we know there is enough msgb data to read a lu->mi_len, so also check that */ + lu = (struct gsm48_loc_upd_req*)gh->data; + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu) + lu->mi_len) + return -EBADMSG; + mi_data = lu->mi; + mi_len = lu->mi_len; + goto got_mi; + + case GSM48_MT_MM_CM_SERV_REQ: + case GSM48_MT_MM_CM_REEST_REQ: + /* Unfortunately in Phase1 the Classmark2 length is variable, so we cannot + * just use gsm48_service_request struct, and need to parse it manually. */ + if (msgb_l3len(msg) < sizeof(*gh) + 2) + return -EBADMSG; + + cm2_len = gh->data[1]; + cm2_buf = gh->data + 2; + goto got_cm2; + + case GSM48_MT_MM_IMSI_DETACH_IND: + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*idi)) + return -EBADMSG; + idi = (struct gsm48_imsi_detach_ind*) gh->data; + mi_data = idi->mi; + mi_len = idi->mi_len; + goto got_mi; + + case GSM48_MT_MM_ID_RESP: + if (msgb_l3len(msg) < sizeof(*gh) + 2) + return -EBADMSG; + mi_data = gh->data+1; + mi_len = gh->data[0]; + goto got_mi; + + default: + break; + } + break; + + case GSM48_PDISC_RR: + + switch (mtype) { + case GSM48_MT_RR_PAG_RESP: + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*paging_response)) + return -EBADMSG; + paging_response = (struct gsm48_pag_resp*)gh->data; + cm2_len = paging_response->cm2_len; + cm2_buf = (uint8_t*)&paging_response->cm2; + goto got_cm2; + + default: + break; + } + break; + } + + return -ENOTSUP; + +got_cm2: + /* MI (Mobile Identity) LV follows the Classmark2 */ + + /* There must be at least a mi_len byte after the CM2 */ + if (cm2_buf + cm2_len + 1 > msg->tail) + return -EBADMSG; + + mi_start = cm2_buf + cm2_len; + mi_len = mi_start[0]; + mi_data = mi_start + 1; + +got_mi: + /* mi_data points at the start of the Mobile Identity coding of mi_len bytes */ + if (mi_data + mi_len > msg->tail) + return -EBADMSG; + + return osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex); +} + +/*! Return a human readable representation of a struct osmo_mobile_identity. + * Write a string like "IMSI-1234567", "TMSI-0x1234ABCD" or "NONE", "NULL". + * \param[out] buf String buffer to write to. + * \param[in] buflen sizeof(buf). + * \param[in] mi Decoded Mobile Identity data. + * \return the strlen() of the string written when buflen is sufficiently large, like snprintf(). + */ +int osmo_mobile_identity_to_str_buf(char *buf, size_t buflen, const struct osmo_mobile_identity *mi) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + if (!mi) + return snprintf(buf, buflen, "NULL"); + OSMO_STRBUF_PRINTF(sb, "%s", gsm48_mi_type_name(mi->type)); + switch (mi->type) { + case GSM_MI_TYPE_TMSI: + OSMO_STRBUF_PRINTF(sb, "-0x%08" PRIX32, mi->tmsi); + break; + case GSM_MI_TYPE_IMSI: + OSMO_STRBUF_PRINTF(sb, "-%s", mi->imsi); + break; + case GSM_MI_TYPE_IMEI: + OSMO_STRBUF_PRINTF(sb, "-%s", mi->imei); + break; + case GSM_MI_TYPE_IMEISV: + OSMO_STRBUF_PRINTF(sb, "-%s", mi->imeisv); + break; + default: + break; + } + return sb.chars_needed; +} + +/*! Like osmo_mobile_identity_to_str_buf(), but return the string in a talloc buffer. + * \param[in] ctx Talloc context to allocate from. + * \param[in] mi Decoded Mobile Identity data. + * \return a string like "IMSI-1234567", "TMSI-0x1234ABCD" or "NONE", "NULL". + */ +char *osmo_mobile_identity_to_str_c(void *ctx, const struct osmo_mobile_identity *mi) +{ + OSMO_NAME_C_IMPL(ctx, 32, "ERROR", osmo_mobile_identity_to_str_buf, mi) +} + +/*! Compare two osmo_mobile_identity structs, returning typical cmp() result. + * \param[in] a Left side osmo_mobile_identity. + * \param[in] b Right side osmo_mobile_identity. + * \returns 0 if both are equal, -1 if a < b, 1 if a > b. + */ +int osmo_mobile_identity_cmp(const struct osmo_mobile_identity *a, const struct osmo_mobile_identity *b) +{ + int cmp; + if (a == b) + return 0; + if (!a) + return -1; + if (!b) + return 1; + cmp = OSMO_CMP(a->type, b->type); + if (cmp) + return cmp; + switch (a->type) { + case GSM_MI_TYPE_TMSI: + return OSMO_CMP(a->tmsi, b->tmsi); + case GSM_MI_TYPE_IMSI: + return strncmp(a->imsi, b->imsi, sizeof(a->imsi)); + case GSM_MI_TYPE_IMEI: + return strncmp(a->imei, b->imei, sizeof(a->imei)); + case GSM_MI_TYPE_IMEISV: + return strncmp(a->imeisv, b->imeisv, sizeof(a->imeisv)); + default: + /* No known type, but both have the same type. */ + return 0; + } +} + /*! Checks is particular message is cipherable in A/Gb mode according to * 3GPP TS 24.008 § 4.7.1.2 * \param[in] hdr Message header @@ -676,7 +1124,8 @@ void gsm48_set_dtx(struct gsm48_cell_options *op, enum gsm48_dtx_mode full, } } -/*! Generate TS 04.08 Mobile ID from TMSI +/*! Deprecated, see osmo_mobile_identity instead. + * Generate TS 04.08 Mobile ID from TMSI * \param[out] buf Caller-provided output buffer (7 bytes) * \param[in] tmsi TMSI to be encoded * \returns number of byes encoded (always 7) */ @@ -692,7 +1141,8 @@ int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi) return 7; } -/*! Generate TS 24.008 §10.5.1.4 Mobile ID of BCD type from ASCII string +/*! Deprecated, see osmo_mobile_identity instead. + * Generate TS 24.008 §10.5.1.4 Mobile ID of BCD type from ASCII string * \param[out] buf Caller-provided output buffer of at least GSM48_MID_MAX_SIZE bytes * \param[in] id Identity to be encoded * \param[in] mi_type Type of identity (e.g. GSM_MI_TYPE_IMSI, IMEI, IMEISV) @@ -724,7 +1174,8 @@ uint8_t gsm48_generate_mid(uint8_t *buf, const char *id, uint8_t mi_type) return 2 + buf[1]; } -/*! Generate TS 04.08 Mobile ID from IMSI +/*! Deprecated, see osmo_mobile_identity instead. + * Generate TS 04.08 Mobile ID from IMSI * \param[out] buf Caller-provided output buffer * \param[in] imsi IMSI to be encoded * \returns number of bytes used in \a buf */ @@ -733,7 +1184,8 @@ int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi) return gsm48_generate_mid(buf, imsi, GSM_MI_TYPE_IMSI); } -/*! Convert TS 04.08 Mobile Identity (10.5.1.4) to string. +/*! Deprecated, see osmo_mobile_identity instead. + * Convert TS 04.08 Mobile Identity (10.5.1.4) to string. * This function does not validate the Mobile Identity digits, i.e. digits > 9 are returned as 'A'-'F'. * \param[out] string Caller-provided buffer for output * \param[in] str_len Length of \a string in bytes diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index ac9aeb21..742cec38 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -363,6 +363,14 @@ gsm48_generate_mid; gsm48_generate_mid_from_imsi; gsm48_generate_mid_from_tmsi; gsm48_mi_to_string; +osmo_mobile_identity_to_str_buf; +osmo_mobile_identity_to_str_c; +osmo_mobile_identity_cmp; +osmo_mobile_identity_decode; +osmo_mobile_identity_decode_from_l3; +osmo_mobile_identity_encoded_len; +osmo_mobile_identity_encode_buf; +osmo_mobile_identity_encode_msgb; gsm48_mm_att_tlvdef; gsm48_number_of_paging_subchannels; gsm48_parse_ra; diff --git a/src/utils.c b/src/utils.c index 18e105f8..3c4a8c9f 100644 --- a/src/utils.c +++ b/src/utils.c @@ -175,6 +175,65 @@ int osmo_bcd2str(char *dst, size_t dst_size, const uint8_t *bcd, int start_nibbl return OSMO_MAX(0, end_nibble - start_nibble); } +/*! Convert string to BCD. + * The given nibble offsets are interpreted in BCD order, i.e. nibble 0 is bcd[0] & 0x0f, nibble 1 is bcd[0] & 0xf0, nibble + * 3 is bcd[1] & 0x0f, etc.. + * \param[out] dst Output BCD buffer. + * \param[in] dst_size sizeof() the output string buffer. + * \param[in] digits String containing decimal or hexadecimal digits in upper or lower case. + * \param[in] start_nibble Offset to start from, in nibbles, typically 1 to skip the first (MI type) nibble. + * \param[in] end_nibble Negative to write all digits found in str, followed by 0xf nibbles to fill any started octet. + * If >= 0, stop before this offset in nibbles, e.g. to get default behavior, pass + * start_nibble + strlen(str) + ((start_nibble + strlen(str)) & 1? 1 : 0) + 1. + * \param[in] allow_hex If false, return error if there are hexadecimal digits (A-F). If true, write those to + * BCD. + * \returns The buffer size in octets that is used to place all bcd digits (including the skipped nibbles + * from 'start_nibble' and rounded up to full octets); -EINVAL on invalid digits; + * -ENOMEM if dst is NULL, if dst_size is too small to contain all nibbles, or if start_nibble is negative. + */ +int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_nibble, int end_nibble, bool allow_hex) +{ + const char *digit = digits; + int nibble_i; + + if (!dst || !dst_size || start_nibble < 0) + return -ENOMEM; + + if (end_nibble < 0) { + end_nibble = start_nibble + strlen(digits); + /* If the last octet is not complete, add another filler nibble */ + if (end_nibble & 1) + end_nibble++; + } + if ((end_nibble / 2) > dst_size) + return -ENOMEM; + + for (nibble_i = start_nibble; nibble_i < end_nibble; nibble_i++) { + uint8_t nibble = 0xf; + int octet = nibble_i >> 1; + if (*digit) { + char c = *digit; + digit++; + if (c >= '0' && c <= '9') + nibble = c - '0'; + else if (allow_hex && c >= 'A' && c <= 'F') + nibble = 0xa + (c - 'A'); + else if (allow_hex && c >= 'a' && c <= 'f') + nibble = 0xa + (c - 'a'); + else + return -EINVAL; + } + nibble &= 0xf; + if ((nibble_i & 1)) + dst[octet] = (nibble << 4) | (dst[octet] & 0x0f); + else + dst[octet] = (dst[octet] & 0xf0) | nibble; + } + + /* floor(float(end_nibble) / 2) */ + return end_nibble / 2; +} + /*! Parse a string containing hexadecimal digits * \param[in] str string containing ASCII encoded hexadecimal digits * \param[out] b output buffer diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c index 96178237..6fef1861 100644 --- a/tests/gsm0408/gsm0408_test.c +++ b/tests/gsm0408/gsm0408_test.c @@ -611,6 +611,368 @@ static void test_mid_decode_zero_length(void) printf("\n"); } +struct msgb *msgb_from_hex(const char *label, uint16_t size, const char *hex) +{ + struct msgb *msg = msgb_alloc_headroom(size, 4, label); + OSMO_ASSERT(msg); + msg->l3h = msgb_put(msg, osmo_hexparse(hex, msg->data, msgb_tailroom(msg))); + return msg; +} + +struct mobile_identity_tc { + const char *label; + const char *compl_l3_msg; + int expect_rc; + struct osmo_mobile_identity expect_mi; +}; + +/* Some Complete Layer 3 messages copied from real GSM network traces. */ +struct mobile_identity_tc mobile_identity_tests[] = { + { + .label = "LU with IMSI 901700000004620", + .compl_l3_msg = "050802008168000130" "089910070000006402", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "901700000004620", + }, + }, + { + .label = "LU with TMSI 0x0980ad8a", + .compl_l3_msg = "05084262f224002a50" "05f40980ad8a", + .expect_mi = { + .type = GSM_MI_TYPE_TMSI, + .tmsi = 0x0980ad8a, + }, + }, + { + .label = "LU with invalid MI type", + .compl_l3_msg = "050802008168000130" "089d10070000006402", + .expect_rc = -EINVAL, + }, + { + .label = "LU with truncated IMSI MI", + .compl_l3_msg = "050802008168000130" "0899100700000064", + .expect_rc = -EBADMSG, + }, + { + .label = "LU with too short IMSI MI (12345)", + .compl_l3_msg = "050802008168000130" "03193254", + .expect_rc = -EBADMSG, + }, + { + .label = "LU with just long enough IMSI MI 123456", + .compl_l3_msg = "050802008168000130" "04113254f6", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "123456", + }, + }, + { + .label = "LU with max length IMSI MI 123456789012345", + .compl_l3_msg = "050802008168000130" "081932547698103254", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "123456789012345", + }, + }, + { + .label = "LU with just too long IMSI MI 1234567890123456", + .compl_l3_msg = "050802008168000130" "091132547698103254f6", + .expect_rc = -EBADMSG, + }, + { + .label = "LU with truncated TMSI MI", + .compl_l3_msg = "05084262f224002a50" "05f40980ad", + .expect_rc = -EBADMSG, + }, + { + .label = "LU with odd length TMSI", + .compl_l3_msg = "05084262f224002a50" "05fc0980ad8a", + .expect_rc = -EBADMSG, + }, + { + .label = "LU with too long TMSI MI", + .compl_l3_msg = "05084262f224002a50" "06f40980ad23", + .expect_rc = -EBADMSG, + }, + { + .label = "LU with too short TMSI", + .compl_l3_msg = "05084262f224002a50" "04f480ad8a", + .expect_rc = -EBADMSG, + }, + { + .label = "CM Service Request with IMSI 123456", + .compl_l3_msg = "052401035058a6" "04113254f6", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "123456", + }, + }, + { + .label = "CM Service Request with TMSI 0x5a42e404", + .compl_l3_msg = "052401035058a6" "05f45a42e404", + .expect_mi = { + .type = GSM_MI_TYPE_TMSI, + .tmsi = 0x5a42e404, + }, + }, + { + .label = "CM Service Request with shorter CM2, with IMSI 123456", + .compl_l3_msg = "052401025058" "04113254f6", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "123456", + }, + }, + { + .label = "CM Service Request with longer CM2, with IMSI 123456", + .compl_l3_msg = "052401055058a62342" "04113254f6", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "123456", + }, + }, + { + .label = "CM Service Request with shorter CM2, with TMSI 0x00000000", + .compl_l3_msg = "052401025058" "05f400000000", + .expect_mi = { + .type = GSM_MI_TYPE_TMSI, + .tmsi = 0, + }, + }, + { + .label = "CM Service Request with invalid MI type", + .compl_l3_msg = "052401035058a6" "089d10070000006402", + .expect_rc = -EINVAL, + }, + { + .label = "CM Service Request with truncated IMSI MI", + .compl_l3_msg = "052401035058a6" "0899100700000064", + .expect_rc = -EBADMSG, + }, + { + .label = "CM Service Request with truncated TMSI MI", + .compl_l3_msg = "0524010150" "05f40980ad", + .expect_rc = -EBADMSG, + }, + { + .label = "CM Service Request with odd length TMSI", + .compl_l3_msg = "052401045058a623" "05fc0980ad8a", + .expect_rc = -EBADMSG, + }, + { + .label = "CM Service Request with too long TMSI MI", + .compl_l3_msg = "052401035058a6" "06f40980ad23", + .expect_rc = -EBADMSG, + }, + { + .label = "CM Service Request with too short TMSI", + .compl_l3_msg = "052401035058a6" "04f480ad8a", + .expect_rc = -EBADMSG, + }, + { + .label = "CM Service Reestablish Request with TMSI 0x5a42e404", + .compl_l3_msg = "052801035058a6" "05f45a42e404", + .expect_mi = { + .type = GSM_MI_TYPE_TMSI, + .tmsi = 0x5a42e404, + }, + }, + { + .label = "Paging Response with IMSI 1234567", + .compl_l3_msg = "06270003505886" "0419325476", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "1234567", + }, + }, + { + .label = "Paging Response with TMSI 0xb48883de", + .compl_l3_msg = "06270003505886" "05f4b48883de", + .expect_mi = { + .type = GSM_MI_TYPE_TMSI, + .tmsi = 0xb48883de, + }, + }, + { + .label = "Paging Response with TMSI, with unused nibble not 0xf", + .compl_l3_msg = "06270003505886" "0504b48883de", + .expect_rc = -EBADMSG, + }, + { + .label = "Paging Response with too short IMEI (1234567)", + .compl_l3_msg = "06270003505886" "041a325476", + .expect_rc = -EBADMSG, + }, + { + .label = "Paging Response with IMEI 123456789012345", + .compl_l3_msg = "06270003505886" "081a32547698103254", + .expect_mi = { + .type = GSM_MI_TYPE_IMEI, + .imei = "123456789012345", + }, + }, + { + .label = "Paging Response with IMEI 12345678901234 (no Luhn checksum)", + .compl_l3_msg = "06270003505886" "0812325476981032f4", + .expect_mi = { + .type = GSM_MI_TYPE_IMEI, + .imei = "12345678901234", + }, + }, + { + .label = "Paging Response with IMEISV 1234567890123456", + .compl_l3_msg = "06270003505886" "091332547698103254f6", + .expect_mi = { + .type = GSM_MI_TYPE_IMEISV, + .imeisv = "1234567890123456", + }, + }, + { + .label = "Paging Response with too short IMEISV 123456789012345", + .compl_l3_msg = "06270003505886" "081b32547698103254", + .expect_rc = -EBADMSG, + }, + { + .label = "Paging Response with too long IMEISV 12345678901234567", + .compl_l3_msg = "06270003505886" "091b3254769810325476", + .expect_rc = -EBADMSG, + }, + { + .label = "Paging Response with IMSI 123456789012345 and flipped ODD bit", + .compl_l3_msg = "06270003505886" "081132547698103254", + .expect_rc = -EBADMSG, + }, + { + .label = "IMSI-Detach with IMSI 901700000004620", + .compl_l3_msg = "050130" "089910070000006402", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "901700000004620", + }, + }, + { + .label = "IMSI-Detach with TMSI 0x0980ad8a", + .compl_l3_msg = "050130" "05f40980ad8a", + .expect_mi = { + .type = GSM_MI_TYPE_TMSI, + .tmsi = 0x0980ad8a, + }, + }, + { + .label = "IMSI-Detach with invalid MI type", + .compl_l3_msg = "050130" "089d10070000006402", + .expect_rc = -EINVAL, + }, + { + .label = "IMSI-Detach with truncated IMSI MI", + .compl_l3_msg = "050130" "0899100700000064", + .expect_rc = -EBADMSG, + }, + { + .label = "IMSI-Detach with too short IMSI MI (12345)", + .compl_l3_msg = "050130" "03193254", + .expect_rc = -EBADMSG, + }, + { + .label = "IMSI-Detach with just long enough IMSI MI 123456", + .compl_l3_msg = "050130" "04113254f6", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "123456", + }, + }, + { + .label = "IMSI-Detach with max length IMSI MI 123456789012345", + .compl_l3_msg = "050130" "081932547698103254", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "123456789012345", + }, + }, + { + .label = "IMSI-Detach with just too long IMSI MI 1234567890123456", + .compl_l3_msg = "050130" "091132547698103254f6", + .expect_rc = -EBADMSG, + }, + { + .label = "IMSI-Detach with truncated TMSI MI", + .compl_l3_msg = "050130" "05f40980ad", + .expect_rc = -EBADMSG, + }, + { + .label = "IMSI-Detach with odd length TMSI", + .compl_l3_msg = "050130" "05fc0980ad8a", + .expect_rc = -EBADMSG, + }, + { + .label = "IMSI-Detach with too long TMSI MI", + .compl_l3_msg = "050130" "06f40980ad23", + .expect_rc = -EBADMSG, + }, + { + .label = "IMSI-Detach with too short TMSI", + .compl_l3_msg = "050130" "04f480ad8a", + .expect_rc = -EBADMSG, + }, + { + .label = "Identity Response with IMSI 901700000004620", + .compl_l3_msg = "0519" "089910070000006402", + .expect_mi = { + .type = GSM_MI_TYPE_IMSI, + .imsi = "901700000004620", + }, + }, + { + .label = "Identity Response with IMEI 123456789012345", + .compl_l3_msg = "0519" "081a32547698103254", + .expect_mi = { + .type = GSM_MI_TYPE_IMEI, + .imei = "123456789012345", + }, + }, + { + .label = "Identity Response with IMEISV 9876543210987654", + .compl_l3_msg = "0519" "099378563412907856f4", + .expect_mi = { + .type = GSM_MI_TYPE_IMEISV, + .imeisv = "9876543210987654", + }, + }, +}; + +void test_struct_mobile_identity() +{ + struct mobile_identity_tc *t; + printf("%s()\n", __func__); + for (t = mobile_identity_tests; (t - mobile_identity_tests) < ARRAY_SIZE(mobile_identity_tests); t++) { + struct osmo_mobile_identity mi; + struct msgb *msg; + int rc; + memset(&mi, 0xff, sizeof(mi)); + + msg = msgb_from_hex(t->label, 1024, t->compl_l3_msg); + rc = osmo_mobile_identity_decode_from_l3(&mi, msg, false); + msgb_free(msg); + + printf("%s: rc = %d", t->label, rc); + if (!rc) { + printf(", mi = %s", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); + } + + if (rc == t->expect_rc + && ((rc != 0) || !osmo_mobile_identity_cmp(&mi, &t->expect_mi))) { + printf(" ok"); + } else { + printf(" ERROR: Expected rc = %d", t->expect_rc); + if (!t->expect_rc) + printf(", mi = %s", osmo_mobile_identity_to_str_c(OTC_SELECT, &t->expect_mi)); + } + printf("\n"); + } + printf("\n"); +} + static const struct bcd_number_test { /* Human-readable test name */ const char *test_name; @@ -1182,6 +1544,7 @@ int main(int argc, char **argv) test_mid_from_imsi(); test_mid_encode_decode(); test_mid_decode_zero_length(); + test_struct_mobile_identity(); test_bcd_number_encode_decode(); test_ra_cap(); test_lai_encode_decode(); diff --git a/tests/gsm0408/gsm0408_test.ok b/tests/gsm0408/gsm0408_test.ok index d343869f..3e6ae1f4 100644 --- a/tests/gsm0408/gsm0408_test.ok +++ b/tests/gsm0408/gsm0408_test.ok @@ -139,6 +139,57 @@ Decoding zero length Mobile Identities rc=1 returned empty string +test_struct_mobile_identity() +LU with IMSI 901700000004620: rc = 0, mi = IMSI-901700000004620 ok +LU with TMSI 0x0980ad8a: rc = 0, mi = TMSI-0x0980AD8A ok +LU with invalid MI type: rc = -22 ok +LU with truncated IMSI MI: rc = -74 ok +LU with too short IMSI MI (12345): rc = -74 ok +LU with just long enough IMSI MI 123456: rc = 0, mi = IMSI-123456 ok +LU with max length IMSI MI 123456789012345: rc = 0, mi = IMSI-123456789012345 ok +LU with just too long IMSI MI 1234567890123456: rc = -74 ok +LU with truncated TMSI MI: rc = -74 ok +LU with odd length TMSI: rc = -74 ok +LU with too long TMSI MI: rc = -74 ok +LU with too short TMSI: rc = -74 ok +CM Service Request with IMSI 123456: rc = 0, mi = IMSI-123456 ok +CM Service Request with TMSI 0x5a42e404: rc = 0, mi = TMSI-0x5A42E404 ok +CM Service Request with shorter CM2, with IMSI 123456: rc = 0, mi = IMSI-123456 ok +CM Service Request with longer CM2, with IMSI 123456: rc = 0, mi = IMSI-123456 ok +CM Service Request with shorter CM2, with TMSI 0x00000000: rc = 0, mi = TMSI-0x00000000 ok +CM Service Request with invalid MI type: rc = -22 ok +CM Service Request with truncated IMSI MI: rc = -74 ok +CM Service Request with truncated TMSI MI: rc = -74 ok +CM Service Request with odd length TMSI: rc = -74 ok +CM Service Request with too long TMSI MI: rc = -74 ok +CM Service Request with too short TMSI: rc = -74 ok +CM Service Reestablish Request with TMSI 0x5a42e404: rc = 0, mi = TMSI-0x5A42E404 ok +Paging Response with IMSI 1234567: rc = 0, mi = IMSI-1234567 ok +Paging Response with TMSI 0xb48883de: rc = 0, mi = TMSI-0xB48883DE ok +Paging Response with TMSI, with unused nibble not 0xf: rc = -74 ok +Paging Response with too short IMEI (1234567): rc = -74 ok +Paging Response with IMEI 123456789012345: rc = 0, mi = IMEI-123456789012345 ok +Paging Response with IMEI 12345678901234 (no Luhn checksum): rc = 0, mi = IMEI-12345678901234 ok +Paging Response with IMEISV 1234567890123456: rc = 0, mi = IMEI-SV-1234567890123456 ok +Paging Response with too short IMEISV 123456789012345: rc = -74 ok +Paging Response with too long IMEISV 12345678901234567: rc = -74 ok +Paging Response with IMSI 123456789012345 and flipped ODD bit: rc = -74 ok +IMSI-Detach with IMSI 901700000004620: rc = 0, mi = IMSI-901700000004620 ok +IMSI-Detach with TMSI 0x0980ad8a: rc = 0, mi = TMSI-0x0980AD8A ok +IMSI-Detach with invalid MI type: rc = -22 ok +IMSI-Detach with truncated IMSI MI: rc = -74 ok +IMSI-Detach with too short IMSI MI (12345): rc = -74 ok +IMSI-Detach with just long enough IMSI MI 123456: rc = 0, mi = IMSI-123456 ok +IMSI-Detach with max length IMSI MI 123456789012345: rc = 0, mi = IMSI-123456789012345 ok +IMSI-Detach with just too long IMSI MI 1234567890123456: rc = -74 ok +IMSI-Detach with truncated TMSI MI: rc = -74 ok +IMSI-Detach with odd length TMSI: rc = -74 ok +IMSI-Detach with too long TMSI MI: rc = -74 ok +IMSI-Detach with too short TMSI: rc = -74 ok +Identity Response with IMSI 901700000004620: rc = 0, mi = IMSI-901700000004620 ok +Identity Response with IMEI 123456789012345: rc = 0, mi = IMEI-123456789012345 ok +Identity Response with IMEISV 9876543210987654: rc = 0, mi = IMEI-SV-9876543210987654 ok + BSD number encoding / decoding test - Running test: regular 9-digit MSISDN - Encoding ASCII (buffer limit=0) '123456789'... diff --git a/tests/utils/utils_test.c b/tests/utils/utils_test.c index e87cb22f..e15cf5f6 100644 --- a/tests/utils/utils_test.c +++ b/tests/utils/utils_test.c @@ -487,6 +487,7 @@ static void bcd2str_test(void) { int i; uint8_t bcd[64]; + uint8_t bcd2[64]; int rc; printf("\nTesting bcd to string conversion\n"); @@ -511,6 +512,12 @@ static void bcd2str_test(void) printf(" ERROR: expected rc=%d\n", t->expect_rc); if (strcmp(str, t->expect_str)) printf(" ERROR: expected result %s\n", osmo_quote_str(t->expect_str, -1)); + + memset(bcd2, 0xff, sizeof(bcd2)); + rc = osmo_str2bcd(bcd2, sizeof(bcd2), str, t->start_nibble, -1, t->allow_hex); + printf("osmo_str2bcd(start_nibble=%d) -> rc=%d\n", t->start_nibble, rc); + if (rc > 0) + printf(" = %s\n", osmo_hexdump(bcd2, rc)); } printf("- zero output buffer\n"); diff --git a/tests/utils/utils_test.ok b/tests/utils/utils_test.ok index baa708ee..cbab72a9 100644 --- a/tests/utils/utils_test.ok +++ b/tests/utils/utils_test.ok @@ -181,27 +181,41 @@ Testing bcd to string conversion - BCD-input='1a 32 54 76 98 f0' nibbles=[1..11[ str_size=64 rc=10 -> "1234567890" +osmo_str2bcd(start_nibble=1) -> rc=6 + = 1f 32 54 76 98 f0 - BCD-input='1a 32 a4 cb 9d f0' nibbles=[1..11[ str_size=64 rc=-22 -> "1234ABCD90" +osmo_str2bcd(start_nibble=1) -> rc=-22 - BCD-input='1a 32 a4 cb 9d f0' nibbles=[1..11[ str_size=64 rc=10 -> "1234ABCD90" +osmo_str2bcd(start_nibble=1) -> rc=6 + = 1f 32 a4 cb 9d f0 - BCD-input='1a 32 54 76 98 f0' nibbles=[1..12[ str_size=64 rc=-22 -> "1234567890F" +osmo_str2bcd(start_nibble=1) -> rc=-22 - BCD-input='1a 32 54 76 98 f0' nibbles=[1..12[ str_size=64 rc=11 -> "1234567890F" +osmo_str2bcd(start_nibble=1) -> rc=6 + = 1f 32 54 76 98 f0 - BCD-input='1a 32 54 76 98 f0' nibbles=[0..12[ str_size=64 rc=12 -> "A1234567890F" +osmo_str2bcd(start_nibble=0) -> rc=6 + = 1a 32 54 76 98 f0 - BCD-input='1a 32 54 76 98 f0' nibbles=[1..12[ str_size=5 rc=11 -> "1234" +osmo_str2bcd(start_nibble=1) -> rc=3 + = 1f 32 f4 - BCD-input='' nibbles=[1..1[ str_size=64 rc=0 -> "" +osmo_str2bcd(start_nibble=1) -> rc=1 + = ff - zero output buffer bcd2str(NULL, ...) -> -12 bcd2str(dst, 0, ...) -> -12 |