天天看點

用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 <iostream>
#include <fstream>
#include <string>
#include <vector>

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 !"<<std::endl;
        return 1;
    }

    std::fstream output;
    output.open(output_file.c_str(), std::fstream::out );
    if ( output.fail() == true)
	{
        std::cout<<"The output file can not be created here !"<<std::endl;
        return 1;
    }

    std::string input_line;
    std::vector<std::string > vec_asn;
    std::vector<std::string >::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<<std::endl;
    }

    input.close();
    output.close();

    return 0;
}
           

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 <asn_application.h>
     22 #include <asn_internal.h>       /* 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的消息解碼器就做成了。