1. 前言
本文简单介绍了IMEI的编码规则,然后引出IMEI校验的一个小程序,可作为C/C++编码爱好者的练手素材。
2. IMEI编码规则
2.1 IMEI组成
IMEI是由15个十进制数字组成的一个字符串,其中包括一个校验位(CD, Check Digit)。下面是3GPP TS23.003的几个截图:
简单地讲,IMEI一个15位,其中最后一位是CD(Check Digit)校验位。
2.2 CD位的计算方法
协议还提供了一个例子:
3. Luhn算法
在协议中,已经提到CD的计算方法是Luhn Algorithm。下面是维基百科对此的介绍:
其中,英文链接中给出了Python语言的校验代码。
4. C/C++语言的校验程序
4.1 原型
在给出了上述背景知识之后,我们提出如下问题:用C/C++语言,写一个函数来检查IMEI号是否合法。原型如下:
/*
0: fail
1: ok
*/
int is_imei_valid(const char *imei);
4.2 参考代码
#include <stdio.h>
#include <ctype.h>
#include <string.h>
const int IMEI_LENGTH = 15;
/*
Get the check digit of Luhn algorithm.
data: the digits to be checked. The last digit is the CD.
len: the length of data/digits.
return:
-1: error occurs
others('0'..'9'): the check digit
*/
int luhn_checksum(const char *data, int len)
{
if (data == NULL || len < 2) return -1;
int even = 1;
int sum = 0;
int double_digit;
char p;
int i;
for (i = len - 2; i >= 0; i--) {
p = data[i];
if (!isdigit(p)) return -1;
p -= '0';
if (even) {
double_digit = p + p;
sum += double_digit / 10 + double_digit % 10;
} else {
sum += p;
}
even = !even;
}
return sum * 9 % 10;
}
/*
return:
-1: error occurs
others('0'..'9'): the check digit
*/
int get_imei_cd(const char *imei)
{
if (imei == NULL) return -1;
if (strlen(imei) != IMEI_LENGTH) return -1;
int cd = luhn_checksum(imei, IMEI_LENGTH);
return cd == -1 ? -1 : cd + '0';
}
/*
0: fail
1: ok
*/
int is_imei_valid(const char *imei)
{
int cd = get_imei_cd(imei);
if (cd == -1) return 0;
return imei[IMEI_LENGTH - 1] == cd;
}
void test_is_imei_valid(const char *testcaseName, const char *imei, int expect)
{
printf("TESTCASE----%s: ", testcaseName);
int result = is_imei_valid(imei);
if (result == expect) {
printf("PASS\n");
} else {
printf("FAIL. expect=%d, real=%d\n", expect, result);
}
}
void test_get_imei_cd(const char *testcaseName, const char *imei, int expect)
{
printf("TESTCASE----%s: ", testcaseName);
int result = get_imei_cd(imei);
if (result == expect) {
printf("PASS\n");
} else {
printf("FAIL. expect=0x%02x, real=0x%02x\n", expect, result);
}
}
int main()
{
printf("test_is_imei_valid\n");
test_is_imei_valid("valid imei 000000000000000", "000000000000000", 1);
test_is_imei_valid("valid imei 012550003170164", "012550003170164", 1);
test_is_imei_valid("valid imei 123412341234564", "123412341234564", 1);
test_is_imei_valid("null imei", NULL, 0);
test_is_imei_valid("invalid imei", "123412341234567", 0);
test_is_imei_valid("invalid imei", "12341234123456", 0);
test_is_imei_valid("invalid imei", "1234123412345678", 0);
printf("test_get_imei_cd\n");
test_get_imei_cd("valid imei 000000000000000", "000000000000000", '0');
test_get_imei_cd("valid imei 012550003170164", "012550003170164", '4');
test_get_imei_cd("valid imei 123412341234564", "123412341234564", '4');
test_get_imei_cd("null imei", NULL, -1);
test_get_imei_cd("invalid imei", "12341234123456", -1);
test_get_imei_cd("invalid imei", "1234123412345678", -1);
return 0;
}
运行结果:
flying-bird@flyingbird:~/docs/The_Road_to_Cpp/src/imei$ gcc imei.c
flying-bird@flyingbird:~/docs/The_Road_to_Cpp/src/imei$ ./a.out
test_is_imei_valid
TESTCASE----valid imei 000000000000000: PASS
TESTCASE----valid imei 012550003170164: PASS
TESTCASE----valid imei 123412341234564: PASS
TESTCASE----null imei: PASS
TESTCASE----invalid imei: PASS
TESTCASE----invalid imei: PASS
TESTCASE----invalid imei: PASS
test_get_imei_cd
TESTCASE----valid imei 000000000000000: PASS
TESTCASE----valid imei 012550003170164: PASS
TESTCASE----valid imei 123412341234564: PASS
TESTCASE----null imei: PASS
TESTCASE----invalid imei: PASS
TESTCASE----invalid imei: PASS
flying-bird@flyingbird:~/docs/The_Road_to_Cpp/src/imei$