ASN.1编解码:asn1c-ORAN-E2AP

Posted rtoax

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASN.1编解码:asn1c-ORAN-E2AP相关的知识,希望对你有一定的参考价值。

ASN.1编解码:asn1c-ORAN-E2AP


荣涛
2021年8月25日


前面的文档讲述了如何编译asn1c,如何选取合适的asn1c软件版本,及其简单使用方法。本文将对asn1c的详细使用进行介绍和分析。并结合 O-RAN E2AP (参考O-RAN.WG3.E2AP-v01.01)进行编码测试与调试。

1. asn1c-s1ap

这个软件提供了三个应用程序,asn1c,unber,enber,他们和应用程序之间的关系为:

2. E2AP-C语言

本章主要讲下面的内容:

  1. 如何使用 asn1c 工具将 ASN.1 编码编译成C语言?
  2. 如何开发 ASN.1 代码?【根据实际情况而定】
  3. 如何生成 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.cInitiatingMessage.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.asn1convertMakefile.am.libasncodecMakefile.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_SRCSASN_MODULE_HDRSASN_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. 分析生成的可执行文件

  1. 先看看能执行吗?
[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)
  1. 再看看有没有额外的依赖

我最关心的是,生成的可执行文件有没有额外的依赖关系,虽然我没有在 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,没有任何其他的依赖,我很开心。

  1. 使用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. 参考链接

  1. https://github.com/nokia/asn1c

以上是关于ASN.1编解码:asn1c-ORAN-E2AP的主要内容,如果未能解决你的问题,请参考以下文章

ASN.1编解码:ORAN-E2AP分析

ASN.1编解码与编程

ASN.1编解码:asn1c的版本分析-诺基亚

ASN.1编解码:asn1c的基本使用

ASN.1编解码:asn1cenber和unber

ASN.1解码