用ASN.1编译工具asn1c生成LTE-RRC消息解码程序

        最近,从iphone手机拿到LTE-RRC通信数据包,需要对LTE-RRC通信数据包进行详细解码。RRC协议都是ASN.1文法描述的,并且是PER编码。用ASN.1描述的协议的解码工作是比较繁琐的。虽然曾经在3G信令系统项目中,开发过一套ASN.1文法转换为C++文法的编码规范,但还是比较复杂,让新人根据这套编码规范实现协议解码比较难上手,并且维护困难。最好是能够利用asn.1文法编译器,把LTE-RRC协议的asn.1描述文件,编译成相应的C/C++文件,然后再用C/C++编译器,编译出LTE-RRC协议编解码器程序。

     asn1c就是这样一个开源的文法编译工具。地址在点击打开链接。取最新版0.9.24用来生成LTE-RRC解码程序。步骤如下:

1、下载asn1c源码、编译asn1c:

下载asn1.1源码:wget http://lionet.info/soft/asn1c-0.9.24.tar.gz

解压:tar xvf asn1c-0.9.24.tar.gz

进入源码目录:cd asn1c-0.9.24

配置:./configure

编译:make

安装:make install

asn1c工具安装好了,可以通过运行 asn1c -h 查看用法

2、准备LTE-RRC协议 的ASN.1描述文件 36331-c00.asn :

从3gpp网站上下载LTE-RRC的规范(3gpp 36331-xxx.doc),例如取最新的Rel 12版,解压,打开word文档,却发现LTE的RRC文档没有把完整的ASN.1描述完整的写在一起,而是分散在各个IE的描述中。印象中3G的RRC规范是写在一块儿的。于是把word文档另存为txt文本文件,用下面的代码,取出asn描述文本,保存为36331-c00.asn:

#include 
#include 
#include 
#include 

int main(int argc, char* argv[])
{
    if (argc != 2)
	{
        return 1;
    }

    std::string output_file;
    std::string input_file = argv[1];

    int pos = input_file.find('.');
    if (pos == std::string::npos )
	{
        output_file = input_file + ".asn";
    }
    else
	{
        output_file = input_file.substr(0,pos) + ".asn";
    }

    std::fstream input;
    input.open(input_file.c_str(), std::fstream::in );
    if ( input.fail() == true)
	{
        std::cout<<"Please check input file is correct !"< vec_asn;
    std::vector::iterator itr;

    const unsigned long cul_asn_idle  = 0x0;
    const unsigned long cul_asn_start = 0x1;

    unsigned long asn_state = cul_asn_idle;

    while ( std::getline(input, input_line) ) 
	{
        if ( cul_asn_idle == asn_state )
		{
            if ( input_line.find("-- ASN1START") != std::string::npos )
			{
                asn_state |=  cul_asn_start;
            }

            continue;
        }

        if ( 0 != (cul_asn_start & asn_state) )
		{
            if ( input_line.find("-- ASN1STOP") != std::string::npos )
			{
                asn_state = cul_asn_idle;
            }
            else
			{
                vec_asn.push_back(input_line);
            }
        }
    }

    for ( itr  = vec_asn.begin(); itr != vec_asn.end(); ++itr )
	{
        output<<*itr<

3、生成C代码:

运行命令 asn1c  -S /usr/local/share/asn1c -fcompound-names -fskeletons-copy -gen-PER -pdu=auto 36331-c00.asn 生成一系列C代码。注意:如果不加-f compound-names,后续c代码编译时,会报很多枚举重定义错误。-fskeleton-copy是会从 -S指定的目录中,拷贝asn基础类型解码文件,为了不存在依赖必须加上。不加-pdu=auto的话,不会产生pdu_colletion.c文件,这个文件中定义了所有的消息PDU。

4、修改生成的C代码:

生成的文件目录下,有一个主程序文件:converter-sample.c和一个makefile文件:Makefile.am.sample。如果直接使用生成的主程序文件,需要在converter-sample.c中增加两行定义:#define PDU BCCH_BCH_Message  和#define ASN_PDU_COLLECTION:见下面红色行

converter-sample.c

 
     21 #include 
     22 #include        /* for _ASN_DEFAULT_STACK_MAX */
     23 
     24 #define PDU BCCH_BCH_Message  // 这个可以从 pdu_collection.c中任选一个,选第一个就可以
     25 #define ASN_PDU_COLLECTION    // 这个是为了在windows下编译,linux下在Makefile.am.sample中已经定义了
     26 
     27 /* Convert "Type" defined by -DPDU into "asn_DEF_Type" */
     28 #define ASN_DEF_PDU(t)  asn_DEF_ ## t
     29 #define DEF_PDU_Type(t) ASN_DEF_PDU(t)
     30 #define PDU_Type        DEF_PDU_Type(PDU)
     31 
     32 extern asn_TYPE_descriptor_t PDU_Type;  /* ASN.1 type to be decoded */
     33 #ifdef  ASN_PDU_COLLECTION              /* Generated by asn1c: -pdu=... */
     34 extern asn_TYPE_descriptor_t *asn_pdu_collection[];
     35 #endif


另外还有一个注意点:

因为PER编码是基于位的,所以一个PER编码的PDU数据,可能不是正好以字节为单位开始和结束。找到文件per_opentype.c文件,修改红色部分:

per_opentype.c:

117		FREEMEM(buf);
118		if(padding >= 8) {
119			ASN_DEBUG("Too large padding %d in open type", (int)padding);
120			padding = padding % 8;  // 加上这行
121			// _ASN_DECODE_FAILED;  // 注释掉这行
122		} else {
123			ASN_DEBUG("Non-zero padding");
124			_ASN_DECODE_FAILED;
125		}
如果不修改,对于大部分数据,可能会解码错误。

5、编译生成的C代码:

运行make -f Makefile.am.sample 就可以生成LTE-RRC的编解码程序,程序名为:progname。

也可以在windows下,创建一个空VC控制台项目,把所有文件加进去,编译windows版本。

6、运行progname就可以对LTE-RRC数据进行解码了,progname -h能显示帮助信息。这个程序跟asn1c已经没有关系了。因为LTE-RRC定义了很多的PDU,所有解码是必须带参数 -p [PUD类型]  ,用progname -p list可以查看支持的PDU类型。

例如,有个数据,PDU是BCCH-DL-SCH-Message,数据文件为BCCH-DL-SCH.dat,内容为十六进制(00 80 1C 31 18 6F E1 22 B8 35 84 96 E2 D0 00 02 00 7D 0E 77 2C B5 50 9B 98 50 28 64 90 99 46 E9 3C 05 04 EE 94 8A 80 00 00):

执行:./progname -p BCCH-DL-SCH-Message BCCH-DL-SCH.dat 即能以XML格式输出详细的解码结果。


一般来讲,不需要使用自动生成的converter-sample.c主程序,需要自己重写一个。因为拿到的LTE-RRC层数据文件中,不可能就只包含一种PDU,而是含不同PUD类型的。需要调用不同的PUD类型对象来解码。这样的话,也就不需要定义#define PDU xxxx 了。例如,本人现在拿到的数据文件,是在每个RRC层PDU前,加了几个字节,分别标识PDU类型和PDU的数据长度。


这样,一个解析LTE-RRC的消息解码器就做成了。


你可能感兴趣的:(C,LTE)