gsm23003: add osmo_imei_str_valid()

Verify 14 digit and 15 digit IMEI strings. OsmoHLR will use the 14
digit version to check IMEIs before writing them to the DB.

Place the Luhn checksum code in a dedicated osmo_luhn() function, so
it can be used elsewhere.

Related: OS#2541
Change-Id: Id2d2a3a93b033bafc74c62e15297034bf4aafe61
This commit is contained in:
Oliver Smith 2019-01-11 13:13:37 +01:00 committed by osmith
parent bd6e7a9f2d
commit 894be2d9da
7 changed files with 107 additions and 0 deletions

View File

@ -140,4 +140,6 @@ const char *osmo_quote_str_buf(const char *str, int in_len, char *buf, size_t bu
uint32_t osmo_isqrt32(uint32_t x);
const char osmo_luhn(const char* in, int in_len);
/*! @} */

View File

@ -93,6 +93,7 @@ struct osmo_guti {
bool osmo_imsi_str_valid(const char *imsi);
bool osmo_msisdn_str_valid(const char *msisdn);
bool osmo_imei_str_valid(const char *imei, bool with_15th_digit);
const char *osmo_mcc_name(uint16_t mcc);
const char *osmo_mnc_name(uint16_t mnc, bool mnc_3_digits);

View File

@ -31,6 +31,7 @@
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
#include <osmocom/core/utils.h>
static bool is_n_digits(const char *str, int min_digits, int max_digits)
{
@ -71,6 +72,23 @@ bool osmo_msisdn_str_valid(const char *msisdn)
return is_n_digits(msisdn, 1, 15);
}
/*! Determine whether the given IMEI is valid according to 3GPP TS 23.003,
* Section 6.2.1. It consists of 14 digits, the 15th check digit is not
* intended for digital transmission.
* \param imei IMEI digits in ASCII string representation.
* \param with_15th_digit when true, expect the 15th digit to be present and
* verify it.
* \returns true when the IMEI is valid, false for invalid characters or number
* of digits.
*/
bool osmo_imei_str_valid(const char *imei, bool with_15th_digit)
{
if (with_15th_digit)
return is_n_digits(imei, 15, 15) && osmo_luhn(imei, 14) == imei[14];
else
return is_n_digits(imei, 14, 14);
}
/*! Return MCC string as standardized 3-digit with leading zeros.
* \param[in] mcc MCC value.
* \returns string in static buffer.

View File

@ -551,6 +551,7 @@ osmo_oap_decode;
osmo_imsi_str_valid;
osmo_msisdn_str_valid;
osmo_imei_str_valid;
osmo_mncc_stringify;
osmo_mncc_names;

View File

@ -765,4 +765,35 @@ const char *osmo_str_toupper(const char *src)
return buf;
}
/*! Calculate the Luhn checksum (as used for IMEIs).
* \param[in] in Input digits in ASCII string representation.
* \param[in] in_len Count of digits to use for the input (14 for IMEI).
* \returns checksum char (e.g. '3'); negative on error
*/
const char osmo_luhn(const char* in, int in_len)
{
int i, sum = 0;
/* All input must be numbers */
for (i = 0; i < in_len; i++) {
if (!isdigit(in[i]))
return -EINVAL;
}
/* Double every second digit and add it to sum */
for (i = in_len - 1; i >= 0; i -= 2) {
int dbl = (in[i] - '0') * 2;
if (dbl > 9)
dbl -= 9;
sum += dbl;
}
/* Add other digits to sum */
for (i = in_len - 2; i >= 0; i -= 2)
sum += in[i] - '0';
/* Final checksum */
return (sum * 9) % 10 + '0';
}
/*! @} */

View File

@ -118,6 +118,47 @@ bool test_valid_msisdn()
return pass;
}
static struct {
bool with_15th_digit;
const char *imei;
bool expect_ok;
} test_imeis[] = {
/* without 15th digit */
{false, "12345678901234", true},
{false, "1234567890123", false},
{false, "123456789012345", false},
/* with 15th digit: valid */
{true, "357613004448485", true},
{true, "357805023984447", true},
{true, "352936001349777", true},
{true, "357663017768551", true},
/* with 15th digit: invalid */
{true, "357613004448480", false},
{true, "357613004448405", false},
{true, "357613004448085", false},
{ NULL, false, false },
};
bool test_valid_imei()
{
int i;
bool pass = true;
bool ok = true;
printf("----- %s\n", __func__);
for (i = 0; i < ARRAY_SIZE(test_imeis); i++) {
ok = osmo_imei_str_valid(test_imeis[i].imei, test_imeis[i].with_15th_digit);
pass = pass && (ok == test_imeis[i].expect_ok);
printf("%2d: expect=%s result=%s imei='%s' with_15th_digit=%s\n",
i, BOOL_STR(test_imeis[i].expect_ok), BOOL_STR(ok),
test_imeis[i].imei, test_imeis[i].with_15th_digit ? "true" : "false");
}
return pass;
}
struct test_mnc_from_str_result {
int rc;
uint16_t mnc;
@ -248,6 +289,7 @@ int main(int argc, char **argv)
pass = pass && test_valid_imsi();
pass = pass && test_valid_msisdn();
pass = pass && test_valid_imei();
pass = pass && test_mnc_from_str();
pass = pass && test_gummei_name();
pass = pass && test_domain_gen();

View File

@ -42,6 +42,18 @@
17: expect=false result=false msisdn='123456 123456'
18: expect=false result=false msisdn='123456 123456'
19: expect=false result=false msisdn='(null)'
----- test_valid_imei
0: expect=true result=true imei='12345678901234' with_15th_digit=false
1: expect=false result=false imei='1234567890123' with_15th_digit=false
2: expect=false result=false imei='123456789012345' with_15th_digit=false
3: expect=true result=true imei='357613004448485' with_15th_digit=true
4: expect=true result=true imei='357805023984447' with_15th_digit=true
5: expect=true result=true imei='352936001349777' with_15th_digit=true
6: expect=true result=true imei='357663017768551' with_15th_digit=true
7: expect=false result=false imei='357613004448480' with_15th_digit=true
8: expect=false result=false imei='357613004448405' with_15th_digit=true
9: expect=false result=false imei='357613004448085' with_15th_digit=true
10: expect=false result=false imei='(null)' with_15th_digit=false
----- test_mnc_from_str
0: "0" rc=0 mnc=0 mnc_3_digits=0 pass
1: "00" rc=0 mnc=0 mnc_3_digits=0 pass