CM xAPP discussion
# connection process with E2T
cmd/ransim/ransim.go -> main()
pkg/manager/manager.go -> Run() -> Start() -> startE2Agents()
pkg/e2agent/agents/agents.go -> Start()
pkg/e2agent/agent.go -> Start()
pkg/controller/connection/controller.go -> NewController()
onos-lib-go/pkg/controller/watcher.go -> Start()
pkg/e2agent/connection/connection.go -> NewE2Connection()
pkg/e2agent/connection/connection.go -> Setup()
pkg/e2agent/connection/connection.go -> connectAndSetup() # connect to the E2T controller
pkg/e2agent/connection/connection.go -> connect() # connect to the E2T controller by STCP Connection
pkg/e2agent/connection/connection.go -> setup() # negotiate E2 setup procedure
pkg/utils/e2ap/setup/setup.go -> NewSetupRequest()
pkg/utils/e2ap/setup/setup.go -> Build()
api/e2ap/v2/e2ap-pdu-contents/e2ap_pdu_contents.pb.go -> E2SetupRequest struct
# RIC address
cmd/ransim/ransim.go -> main() -> modelName -> cfg
pkg/manager/manager.go -> NewManager()
pkg/manager/manager.go -> Run() -> Start() -> model.Load()
pkg/model/load.go -> Load() -> LoadConfig()
pkg/manager/manager.go -> Run() -> Start() -> startE2Agents()
pkg/e2agent/agents/agents.go -> NewE2Agents()
pkg/e2agent/agent.go -> NewE2Agent()
pkg/model/model.go -> GetController().Address
#overview of ransim
# STCP Connection
# Sevcie ModelRegistry (HMO example)
cmd/ransim/ransim.go -> main() -> modelName -> cfg
pkg/manager/manager.go -> NewManager() -> RegisterModelPlugin()
pkg/modelplugins/registry.go -> RegisterModelPlugin()
pkg/manager/manager.go -> Run() -> Start() -> model.Load()
pkg/e2agent/agents/agents.go -> NewE2Agents()
pkg/e2agent/agent.go -> NewE2Agent()
pkg/servicemodel/registry/registry.go -> NewServiceModelRegistry()
pkg/servicemodel/mho/mho.go -> NewServiceModel()
onos-e2-sm/servicemodels/e2sm_mho_go/servicemodel/servicemodel.go -> RanFuncDescriptionProtoToASN1()
pkg/servicemodel/registry/registry.go -> RegisterServiceModel()
pkg/e2agent/agent.go -> Start()
pkg/e2agent/connection/connection.go -> NewE2Connection()
# Sevcie Model execute (RICSubscription)
# startNorthboundServer
cmd/ransim/ransim.go -> main()
pkg/manager/manager.go -> Run() -> Start() -> startNorthboundServer() # Start gRPC server
# E2ConnectionUpdate
# processNodeEvents
- https://github.com/nokia/asn1c
- https://www.cxyzjd.com/article/peng_yw/22437251
- https://webcache.googleusercontent.com/search?q=cache:WJRr26A-Af8J:https://www.iitter.com/other/43889.html+&cd=6&hl=zh-CN&ct=clnk&gl=us
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)
这个可执行文件是比较复杂的,具体怎么使用,后续文档中再做解释,本文先到这。
REF:
[4G&5G专题-60]:L3 RRC层 - 定义数据类型与数据结构的超级神器:ASN.1抽象语法标记