ASN.1编解码:asn1c-ORAN-E2AP
Posted rtoax
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASN.1编解码:asn1c-ORAN-E2AP相关的知识,希望对你有一定的参考价值。
前面的文档讲述了如何编译asn1c,如何选取合适的asn1c软件版本,及其简单使用方法。本文将对asn1c的详细使用进行介绍和分析。并结合 O-RAN E2AP (参考O-RAN.WG3.E2AP-v01.01)进行编码测试与调试。
1. asn1c-s1ap
这个软件提供了三个应用程序,asn1c
,unber
,enber
,他们和应用程序之间的关系为:
2. E2AP-C语言
本章主要讲下面的内容:
- 如何使用 asn1c 工具将 ASN.1 编码编译成C语言?
- 如何开发 ASN.1 代码?【根据实际情况而定】
- 如何生成 ASN.1 二进制流?【不懂不会】
2.1. 如何使用 asn1c 工具将 ASN.1 编码编译成C语言?
这个步骤是繁琐的,为了尽可能清晰,我将写个脚本,简化操作流程,同时,我也将用 CMake 简化编译流程。
2.1.1. 准备 ASN.1 文件
这里我直接使用下面的两个文件,
e2ap-v01.00.00.asn
:E2 Termination中提供;e2ap-v01.01.asn1
:我从 O-Ran 文档中提取出的 ASN.1;
这两个文件内容较多,不贴出全部,只给出以小部分内容,以e2ap-v01.00.00.asn
为例,文件中定义了一些枚举和结构体,以其中的InitiatingMessage
为例:
InitiatingMessage ::= SEQUENCE {
procedureCode E2AP-ELEMENTARY-PROCEDURE.&procedureCode ({E2AP-ELEMENTARY-PROCEDURES}),
criticality E2AP-ELEMENTARY-PROCEDURE.&criticality ({E2AP-ELEMENTARY-PROCEDURES}{@procedureCode}),
value E2AP-ELEMENTARY-PROCEDURE.&InitiatingMessage ({E2AP-ELEMENTARY-PROCEDURES}{@procedureCode})
}
使用 asn1c 编译InitiatingMessage
后将生成两个文件 InitiatingMessage.c
和 InitiatingMessage.h
,看一下结构体
/* InitiatingMessage */
typedef struct InitiatingMessage {
ProcedureCode_t procedureCode;
Criticality_t criticality;
struct InitiatingMessage__value {
InitiatingMessage__value_PR present;
union InitiatingMessage__value_u {
RICsubscriptionRequest_t RICsubscriptionRequest;
RICsubscriptionDeleteRequest_t RICsubscriptionDeleteRequest;
RICserviceUpdate_t RICserviceUpdate;
RICcontrolRequest_t RICcontrolRequest;
E2setupRequest_t E2setupRequest;
ResetRequest_t ResetRequest;
RICindication_t RICindication;
RICserviceQuery_t RICserviceQuery;
ErrorIndication_t ErrorIndication;
} choice;
/* Context for parsing across buffer boundaries */
asn_struct_ctx_t _asn_ctx;
} value;
/* Context for parsing across buffer boundaries */
asn_struct_ctx_t _asn_ctx;
} InitiatingMessage_t;
再过复杂的问题此处不再讲解,因为我也不懂。
2.1.2. 用ASN.1 文件生成C语言
大家想关注的是如何将 ASN.1 代码编译成 C语言文件的,直接上代码吧:
asn1c -fcompound-names \\
-fincludes-quoted \\
-fno-include-deps \\
-findirect-choice \\
-gen-PER -D. \\
e2ap-v01.00.00.asn
上面的参数,我也不适很懂,就这么用吧,需要注意的是,在执行上面的指令后,会生成很多的源文件,我们先来关注Makefile.am.asn1convert
和 Makefile.am.libasncodec
。Makefile.am.asn1convert
和上面的指令的功能基本相同,这里不做解释,直接看下里面的内容:
# [rongtao@localhost e2ap]$ more Makefile.am.asn1convert
include ./Makefile.am.libasncodec
bin_PROGRAMS += asn1convert
asn1convert_CFLAGS = $(ASN_MODULE_CFLAGS) -DASN_PDU_COLLECTION
asn1convert_CPPFLAGS = -I$(top_srcdir)/./
asn1convert_LDADD = libasncodec.la
asn1convert_SOURCES = \\
./converter-example.c\\
./pdu_collection.c
regen: regenerate-from-asn1-source
regenerate-from-asn1-source:
asn1c -fcompound-names -fincludes-quoted -fno-include-deps -findirect-choice -gen-PER -D. e2ap-v01.00.00.asn
在上面的makefile文件中可以看到文件Makefile.am.libasncodec
,这个文件中定义了ASN_MODULE_SRCS
、ASN_MODULE_HDRS
、ASN_MODULE_CFLAGS
以及下面的变量:
lib_LTLIBRARIES+=libasncodec.la
libasncodec_la_SOURCES=$(ASN_MODULE_SRCS) $(ASN_MODULE_HDRS)
libasncodec_la_CPPFLAGS=-I$(top_srcdir)/./
libasncodec_la_CFLAGS=$(ASN_MODULE_CFLAGS)
libasncodec_la_LDFLAGS=-lm
后续,我们如果再生成C语言,即可使用下面的命令:
make -f Makefile.am.asn1convert regen
上面的指令也是我再写这个文档的时候才发现的。
2.1.3. 生成的C语言源文件的编译
编译是个难题,我们可以先看一下生成的测试例文件converter-example.c
,该源文件中有个宏定义,该宏定义指定,当前文件要测试哪个数据结构,宏定义的使用如下:
/* Convert "Type" defined by -DPDU into "asn_DEF_Type" */
#ifdef PDU
#define ASN_DEF_PDU(t) asn_DEF_ ## t
#define DEF_PDU_Type(t) ASN_DEF_PDU(t)
#define PDU_Type DEF_PDU_Type(PDU)
extern asn_TYPE_descriptor_t PDU_Type; /* ASN.1 type to be decoded */
#define PDU_Type_Ptr (&PDU_Type)
#else /* !PDU */
#define PDU_Type_Ptr NULL
#endif /* PDU */
我以RICcontrolRequest
举例,该结构在e2ap-v01.00.00.asn
中的定义为:
RICcontrolRequest ::= SEQUENCE {
protocolIEs ProtocolIE-Container {{RICcontrolRequest-IEs}},
...
}
在生成的头文件RICcontrolRequest.h
中定义了这个数据结构
/* RICcontrolRequest */
typedef struct RICcontrolRequest {
ProtocolIE_Container_1544P7_t protocolIEs;
/*
* This type is extensible,
* possible extensions are below.
*/
/* Context for parsing across buffer boundaries */
asn_struct_ctx_t _asn_ctx;
} RICcontrolRequest_t;
同时,该文件下方有一些声明
/* Implementation */
extern asn_TYPE_descriptor_t asn_DEF_RICcontrolRequest;
extern asn_SEQUENCE_specifics_t asn_SPC_RICcontrolRequest_specs_1;
extern asn_TYPE_member_t asn_MBR_RICcontrolRequest_1[1];
其中asn_DEF_RICcontrolRequest
即为PDU定义为RICcontrolRequest
的宏定义ASN_DEF_PDU
展开值,
#define ASN_DEF_PDU(t) asn_DEF_ ## t
#define DEF_PDU_Type(t) ASN_DEF_PDU(t)
#define PDU_Type DEF_PDU_Type(PDU)
所以,我在 Makefile /CMakeLists.txt 中添加宏定义 -DPDU=RICcontrolRequest
,接着进行正常的编译即可,我使用的 CMakeLists.txt
,文件内容如下:
###################################################
# 编译使用 asn1c 编译 ASN.1 文件而生成的 C语言 程序
#
# 作者:荣涛
# 时间:2021年8月
###################################################
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
PROJECT(e2ap)
aux_source_directory(./ DIR_SRCS)
include_directories(./)
find_library(CONFIG config /usr/lib64)
link_libraries(${CONFIG})
add_definitions( -MD -Wall -DPDU=RICcontrolRequest)
ADD_EXECUTABLE(test ${DIR_SRCS})
接下来进行编译即可:
[rongtao@localhost e2ap]$ cd build/
[rongtao@localhost build]$ cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /home/rongtao/test/ASN.1/asn1c/jt_sran/e2ap/build
[rongtao@localhost build]$ make
[ 0%] Building C object CMakeFiles/test.dir/ANY.c.o
[ 1%] Building C object CMakeFiles/test.dir/BIT_STRING.c.o
[ 2%] Building C object CMakeFiles/test.dir/BIT_STRING_oer.c.o
[此处省略一万行。。。]
[ 98%] Building C object CMakeFiles/test.dir/xer_encoder.c.o
[ 99%] Building C object CMakeFiles/test.dir/xer_support.c.o
[100%] Linking C executable test
[100%] Built target test
[rongtao@localhost build]$
看一眼当前目录中,生成了可执行文件test
,至此,我已经讲完了由 asn1c 生成的C语言文件的编译过程。置于如何进行测试,下一章再说。这里,我把上面的步骤谢了一个脚本,可以用,也可以不用。
#!/bin/bash
#
# 将 O-RAN E2AP ASN.1 转化为 C语言
# 理论上,这个脚本并不限于 E2AP,其他 由 Nokia 发布的
# O-RAN 文档 中的 ASN.1 均可由 此脚本进行C语言的生成,
#
# 荣涛 rongtao@sylincom.com
# 2021年8月
#
# 默认的 ASN.1 文件
file_asn1=""
DEFAULT_GEN_DEMO="converter-example.c"
function INFO() {
echo -e "\\033[1;34m $1 \\033[0m"
}
function ERROR() {
echo -e "\\033[1;31m $1 \\033[0m"
}
# 帮助信息
function usage() {
echo ""
echo Usage: ./genc.sh [ASN.1 file]
echo ""
echo " [ASN.1 file] is ASN.1 file from some where that i dont know."
echo ""
echo " You must install asn1c-s1ap, download in https://github.com/nokia/asn1c, version is v0.9.29"
echo " asn1c's version must be v0.9.29"
echo ""
}
# 检查软件是否安装,版本是否对应
function check_asn1c() {
which asn1c > /dev/null
if [ $? != 0 ]; then
ERROR "FATAL: asn1c not install"
exit 1
fi
# 必须使用 Nokia 的 https://github.com/nokia/asn1c ,也就是 asn1c-s1ap
if [ $(asn1c -v 2>&1 | grep ASN | awk '{print $3}') != "v0.9.29" ]; then
ERROR "FATAL: wrong asn1c version, must v0.9.29(https://github.com/nokia/asn1c)"
exit 1
fi
}
# 检查 入参,以及 ASN.1 文件是否存在
function check_asn1_file() {
if [ $# -lt 1 ]; then
usage
exit 1
fi
file_asn1=$1
if [ ! -f $file_asn1 ]; then
ERROR "FATAL: file \\"$file_asn1\\" not exist."
exit 1
fi
}
# 使用 asn1c 编译
function compile_asn1_file() {
# 编译成 C语言
asn1c -fcompound-names \\
-fincludes-quoted \\
-fno-include-deps \\
-findirect-choice \\
-gen-PER -D. \\
$file_asn1
}
# 修改 生成的 C语言测试文件
function modify_test_demo() {
if [ ! -f $DEFAULT_GEN_DEMO ]; then
WARNING "WARNING: file \\"$DEFAULT_GEN_DEMO\\" not exist."
return 1
fi
echo "#include <stdio.h>" > $DEFAULT_GEN_DEMO
echo "" >> $DEFAULT_GEN_DEMO
echo "int main(int argc, char *argv[])" >> $DEFAULT_GEN_DEMO
echo "{" >> $DEFAULT_GEN_DEMO
echo " printf(\\"ASN.1 test running.\\n\\");" >> $DEFAULT_GEN_DEMO
echo " return 0;" >> $DEFAULT_GEN_DEMO
echo "}" >> $DEFAULT_GEN_DEMO
}
# 检查软件是否安装,版本是否对应
INFO "Check asn1c software"
check_asn1c
INFO "Check asn1c software, OK"
# 检查 输入的文件
INFO "Check ASN.1 file"
check_asn1_file $*
INFO "Check ASN.1 file, OK"
# 使用 asn1c 编译
INFO "Compile ASN.1 file"
compile_asn1_file $file_asn1
INFO "Compile ASN.1 file, DONE"
# 修改 自动生成的 测试代码
#INFO "Modify C file"
#modify_test_demo
#INFO "Modify C file, DONE"
INFO ""
INFO "Now, you can do some thing like:"
INFO "$ mkdir build"
INFO "$ cd build"
INFO "$ cmake .."
INFO "$ make"
INFO "$ ./test"
2.1.4. 分析生成的可执行文件
- 先看看能执行吗?
[rongtao@localhost e2ap]$ ./test
./test: No input files specified. Try '-h' for more information
[rongtao@localhost e2ap]$ ./test -h
Usage: ./test [options] <datafile> ...
Where options are:
-iber Input is in BER (Basic Encoding Rules) or DER
-ioer Input is in OER (Octet Encoding Rules)
-iper Input is in Unaligned PER (Packed Encoding Rules) (DEFAULT)
-iaper Input is in Aligned PER (Packed Encoding Rules)
-ixer Input is in XER (XML Encoding Rules)
-oder Output as DER (Distinguished Encoding Rules)
-ooer Output as Canonical OER (Octet Encoding Rules)
-oper Output as Unaligned PER (Packed Encoding Rules)
-oaper Output as Aligned PER (Packed Encoding Rules)
-oxer Output as XER (XML Encoding Rules) (DEFAULT)
-otext Output as plain semi-structured text
-onull Verify (decode) input, but do not output
-per-nopad Assume PER PDUs are not padded (-iper)
-1 Decode only the first PDU in file
-b <size> Set the i/o buffer size (default is 8192)
-c Check ASN.1 constraints after decoding
-d Enable debugging (-dd is even better)
-n <num> Process files <num> times
-s <size> Set the stack usage limit (default is 30000)
- 再看看有没有额外的依赖
我最关心的是,生成的可执行文件有没有额外的依赖关系,虽然我没有在 CMakeLists.txt中加任何的动态库链接,但是为了验证,还是看看:
[rongtao@localhost e2ap]$ ldd test
linux-vdso.so.1 => (0x00007ffdcf9df000)
libconfig.so.9 => /usr/lib64/libconfig.so.9 (0x00007f2e2a8bf000)
libc.so.6 => /usr/lib64/libc.so.6 (0x00007f2e2a4f1000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2e2aacb000)
GOOD,没有任何其他的依赖,我很开心。
- 使用nm查看符号表
[rongtao@localhost e2ap]$ nm test
000000000044bd2b t add_bytes_to_buffer
000000000040b714 t ANY__consume_bytes
000000000040be42 T ANY_decode_aper
000000000040b82b T ANY_decode_uper
000000000040c0fa T ANY_encode_aper
【此处省略一万行。。。】
0000000000452af6 t xer__print2fp
00000000004526e8 T xer_skip_unknown
0000000000451d7d t xer__token_cb
000000000045267a T xer_whitespace_span
0000000000681cd0 b zeros.3886
0000000000681cc0 b zeros.4008
2.1.5. 运行生成的可执行文件
上面已经给出了该测试文件的帮助信息,但是这里再给出一遍吧
[rongtao@localhost e2ap]$ ./test -h
Usage: ./test [options] <datafile> ...
Where options are:
-iber Input is in BER (Basic Encoding Rules) or DER
-ioer Input is in OER (Octet Encoding Rules)
-iper Input is in Unaligned PER (Packed Encoding Rules) (DEFAULT)
-iaper Input is in Aligned PER (Packed Encoding Rules)
-ixer Input is in XER (XML Encoding Rules)
-oder Output as DER (Distinguished Encoding Rules)
-ooer Output as Canonical OER (Octet Encoding Rules)
-oper Output as Unaligned PER (Packed Encoding Rules)
-oaper Output as Aligned PER (Packed Encoding Rules)
-oxer Output as XER (XML Encoding Rules) (DEFAULT)
-otext Output as plain semi-structured text
-onull Verify (decode) input, but do not output
-per-nopad Assume PER PDUs are not padded (-iper)
-1 Decode only the first PDU in file
-b <size> Set the i/o buffer size (default is 8192)
-c Check ASN.1 constraints after decoding
-d Enable debugging (-dd is even better)
-n <num> Process files <num> times
-s <size> Set the stack usage limit (default is 30000)
这个可执行文件是比较复杂的,具体怎么使用,后续文档中再做解释,本文先到这。
3. 参考链接
以上是关于ASN.1编解码:asn1c-ORAN-E2AP的主要内容,如果未能解决你的问题,请参考以下文章