使用asn1tools進行asn1編解碼
最近在做3GPP的編解碼,發現有兩個第三方庫比較好用。一個是ASN1C(c語言編譯環境),一個是python第三方庫asn1tools。這裡介紹下asn1tools的使用方法:
1 第一步:生成asn檔案
將需要編碼的資料結構儲存在asn字尾名的檔案中
3GPP中的結構如下:
-- ASN1START
BCCH-BCH-Message-NB ::= SEQUENCE {
message BCCH-BCH-MessageType-NB
}
BCCH-BCH-MessageType-NB::= MasterInformationBlock-NB
-- ASN1STOP
對應的.asn檔案的基本結構如下:也就是講ASN1START和ASN1STOP中的資料提取出來。然後上asn自己的頭資訊
EUTRA-RRC-Definitions DEFINITIONS AUTOMATIC TAGS ::=
BEGIN
BCCH-BCH-Message ::= SEQUENCE {
message BCCH-BCH-MessageType
}
END
在3GPP中有大量的類似結構,如果一個個手動的拷貝,太耗費時間了。因此用下面的程式碼將3GPP中的資料結構自動提取出來儲存在asn檔案中。程式碼如下:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int main()
{
std::string output_file;
std::string input_file = "D:/code_block_prj/gen_asn/protol.txt";
std::cout<<input_file.c_str()<<std::endl;
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;
}
2:開啟36331的word文件並另存為txt檔案
3:執行上面的程式,其中input_file就是儲存txt檔案的位置,需要自己設定。執行完後會在本地資料夾下面生成一個asn檔案
第二步:利用asn1tools進行編解碼:
一:首先pip3 install asn1tools進行模組安裝
二:在3GPP中有大量的資料結構,例如sequence, bit string, octer string, bool, sequence of等等,這些結構體在python對應的結構體如下表。
ASN.1 type |
Python type |
Example |
|
BOOLEAN |
bool |
True 'ackNackSRS-SimultaneousTransmission': True |
|
INTEGER |
int |
87 'p0-NominalPUCCH': -127, |
|
REAL |
float |
33.12 |
|
NULL |
– |
None |
|
BIT STRING |
tuple(bytes, int) |
(b'\x50', 4) 元組第一個引數為值,第二個引數為bit長度 示例: ac-BarringForSpecialAC BIT STRING (SIZE(5)) 'ac-BarringForSpecialAC': (b'\xf0', 5) |
|
OCTET STRING |
bytes |
b'\x44\x1e\xff' hnb-Name OCTET STRING (SIZE(1..48)) 'hnb-Name': b'4' |
|
OBJECT IDENTIFIER |
str |
'1.33.2' |
|
ENUMERATED |
str |
'one' ac-BarringTime ENUMERATED {s4, s8, s16, s32, s64, s128, s256, s512}, 程式碼: 'ac-BarringTime': 's128', |
|
SEQUENCE |
dict |
{'a': 52, 'b': 1} |
|
SEQUENCE OF |
list |
[1, 3]採用list列表的方法[] 示例一: InterFreqCarrierFreqList ::= SEQUENCE (SIZE (1..maxFreq)) OF InterFreqCarrierFreqInfo
|
|
SET |
dict |
{'foo': 'bar'} |
|
SET OF |
list |
[3, 0, 7] |
|
CHOICE |
tuple |
('a', 5) |
|
UTF8String |
str |
'hello' |
|
NumericString |
str |
'234359' |
|
PrintableString |
str |
'goo' |
|
IA5String |
str |
'name' |
|
VisibleString |
str |
'gle' |
|
GeneralString |
str |
'abc' |
|
BMPString |
str |
'ko' |
|
GraphicString |
str |
'a b' |
|
TeletexString |
str |
'ßø' |
|
UniversalString |
str |
'åäö' |
|
UTCTime |
datetime.datetime |
datetime(2018, 6, 11) |
|
GeneralizedTime |
datetime.datetime |
datetime(2018, 1, 31) |
|
ObjectDescriptor |
– |
– |
三:對結構進行賦值。以BCCH-DL-SCH-Message-NB結構為例,首先需要根據BCCH-DL-SCH-Message-NB的結構用python的結構體進行賦值。如下所示。具體的賦值方法參考上面的表格。
BCCH_DL_SCH_Message_NB={
'message':(
'c1',(
'systemInformationBlockType1-r13',{
'hyperSFN-MSB-r13':(b'\x07',8),
'cellAccessRelatedInfo-r13':{
'plmn-IdentityList-r13':[
{'plmn-Identity-r13':{'mcc':[0,0,1],'mnc':[0,1]},
'cellReservedForOperatorUse-r13':'notReserved',
'attachWithoutPDN-Connectivity-r13':'true'}],
'trackingAreaCode-r13':(b'\x00\x01',16),
'cellIdentity-r13':(b'\x00\x01\x10\x10',28),
'cellBarred-r13':'notBarred',
'intraFreqReselection-r13':'notAllowed',
},
'cellSelectionInfo-r13':{
'q-RxLevMin-r13':-53,
'q-QualMin-r13':-20
},
'freqBandIndicator-r13':8,
'schedulingInfoList-r13':[{'si-Periodicity-r13':'rf64','si-RepetitionPattern-r13':'every8thRF','sib-MappingInfo-r13':[],'si-TB-r13':'b552'}],
'si-WindowLength-r13':'ms160'
}
)
)
}
四:進行編碼。在encode函式中第一個引數就是asn檔案中的結構體名稱。第二個引數就是上面賦值的字典結構。最終得到16進位制的碼流
def asn1tools__3GPP():
foo = asn1tools.compile_files('protol.asn', 'uper')
encoded = foo.encode('BCCH-DL-SCH-Message-NB',BCCH_DL_SCH_Message_NB)
print(encoded.hex())
五:資料視覺化:16進位制的碼流對於觀測不方便。因此將前面編碼得到的16進位制碼流再進行解碼並儲存在json檔案中,然後通過jsonViewer工具進行檢視。程式碼如下
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, bytes):
return str(obj, encoding='utf-8');
return json.JSONEncoder.default(self, obj)
def asn1tools__3GPP():
foo = asn1tools.compile_files('protol.asn', 'uper')
encoded = foo.encode('BCCH-DL-SCH-Message-NB',BCCH_DL_SCH_Message_NB)
print(encoded.hex())
decoded=foo.decode('BCCH-DL-SCH-Message-NB',encoded)
value=json.dumps(decoded,indent=1,cls=MyEncoder,ensure_ascii=False)
print(decoded,value)
f=open('BCCH.json','wb')
f.write(value.encode("utf-8"))
f.close()
在jsonViewer中開啟json檔案,可以更直觀的觀測結構
但是這樣會有一個問題,在編譯BIT STRING或者OCTER STRING的時候,編譯完後的資料無法寫入json檔案。原因在於json檔案是utf-8的編碼格式。某些位元組utf-8無法識別,例如0x80這樣的資料。如果將trackingAreaCode-r13改成如下的值,那麼在寫入json檔案的時候就會提示utf-8 can’t decode \0x80的錯誤
'trackingAreaCode-r13':(b'\x80\x01',16),
那麼程式碼修改如下。用uper和jer兩種編碼方式編譯asn檔案。然後將UPER解碼得到的資料用jer的方法進行編碼。然後再寫入json檔案。
def asn1tools__3GPP():
foo = asn1tools.compile_files('protol.asn', 'uper')
foo_jer=asn1tools.compile_files('protol.asn', 'jer')
encoded = foo.encode('BCCH-DL-SCH-Message-NB',BCCH_DL_SCH_Message_NB)
print(encoded.hex())
decoded=foo.decode('BCCH-DL-SCH-Message-NB',encoded)
value_jer = foo_jer.encode('BCCH-DL-SCH-Message-NB', decoded)
with open('BCCH.json','wb') as f:
f.write(value_jer)
這樣做的原理是UPER是將資料以位元組的形式編碼,而jer是以字串的形式編碼。因此寫入json檔案沒有任何問題