【翻译】第四章 MMS-EASE Lite底层

**概要:**本节内容描述了MMS-EASE Lite栈组件间的关系。

1 配置文件选项(Profile Options)

“MMS-EASE Lite栈组件”是一种基于不同的OSI协议层的实现。它专门为一些硬件资源非常有限的系统而设计,比如某些嵌入式系统,它具有模块化的特点,以便在实际使用时可以仅仅调用特定应用程序所需的协议层。它由几个C代码模块组成,可以方便地在任何嵌入式系统中编译。除了几个简单的函数(隔离在tp4port.c模块中)以外,MMS-EASE Lite全部使用ANSI标准C文件。在OSI七层参考模型的术语中,每个协议层都被描述为其上层提供“服务”。在本应用中,这些“服务”是通过一系列API来提供的。下图显示了OSI协议层与连接它们的api之间的关系。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2 底层组件移植(Lower Layer Component Portation)

2.1 OSI传输层(TP4)移植

对于新的操作系统或硬件平台,需要移植以下函数:

tp4_init_timer
tp4_check_timer

编译选项的配置:

必选编译选项:-DLEAN_T,功能:编译该版本的TP4 API;

可选编译选项:-DDEBUG_SISCO,功能:使用“slog”启用日志记录

mvl_init_glb_vars

函数名mvl_init_glb_vars
功能在TP4初始化过程(tp4_initialize))中被调用,用于初始化定时器服务
函数原型ST_VOID tp4_init_timer (ST_VOID);
参数
返回值ST_VOID

tp4_check_timer

函数名mvl_init_glb_vars
功能被tp4_event调用。该函数负责监视定时器,每当计时达到1s就调用函数tp4_timer_tick。由sisco提供的tp4_check_timer示例函数适用于大多数系统,但如果目标系统上有更有效的方法,用户也可自由修改该函数。在事件驱动的系统上,重要的是要确保mvl_comm_serve至少每秒钟调用一次,这样这个函数也会被调用。因此,系统休眠时间不应超过1秒。然而,这个限制仅在正在使用TP4服务时生效
函数原型ST_VOID tp4_init_timer (ST_VOID);
参数
返回值ST_VOID

2.2 OSI子网络层移植

用户必须提供子网API。更多信息请参阅附录G。

2.3 TCP/IP(via RFC1006)移植

TCP/IP(via RFC1006)协议栈由以下组件组成:

ACSE
OSI演示
OSI对话
TP0(OSI传输层类0)
TCP/IP(由操作系统通过socket提供)

socket接口实现概述

MMS-EASE Lite的sockets接口包含三个任务,每个任务具有阻塞能力(用于等待来自用户或其他任务的消息),如下所示:

Main任务 (每台主机上仅有一个)
Listen任务 (用于监听socket,每台主机上仅有一个)
Read任务 (用于读取socket,每个连接仅有一个)

Main任务包括所有MMS的编解码进程以及用户接口。在启动时,它会生成Listen任务,该任务会监听是否存在被建立的连接。当检测到已建立的连接时,Listen任务向Main任务发送管道消息,Main任务调用" accept "来接收消息,并生成一个Read任务。

如果要进行向外传输的连接,Main任务会执行一个非阻塞连接调用(可参考tp0_sock.c和p_connect_req),然后生成一个Read任务实例,该实例会等待连接完成。

在两种情况(传入或传出)下,当连接完成后,Read任务会接收连接中的包并将它们传递给Main任务。所有连接中的数据包都是由Main任务发送的。

Main任务含有多层函数调用,因此有时很难跟踪用户级函数是如何在最低层实现的。例如,处理传入事件的函数调用层级如下:

main (\mmslite\mvl\usr\server\server.c) 调用  /*主函数*/
mvl_comm_serve (\mmslite\mvl\src\mvl_serv.c) 调用
mvl_net_service (\mmslite\mvl\src\acse\mvl_acse.c) 调用
copp_event (\mmslite\uca\acse\acse2enc.c) 调用
tp0_event (\mmslite\uca\leant\tp0main.c) 调用
np_event (\mmslite\uca\leant\tp0_unix.c)   /*最底层实现*/

通过观察np_event函数的内容我们可以发现,函数开头部分的代码含义是等待来自Read任务(sreadd)或Listen任务(sreadd)的管道消息。

slistend (\mmslite\uca\leant\tp0_list.c)
sreadd (\mmslite\uca\leant\tp0_read.c)

注:任务sreadd和slistend是从MMS-EASE Lite应用程序内部生成的。它们通常位于在mmslite/bin目录中,但默认情况下该目录并不被包含在环境变量中。可以通过将目录/mmslite/bin添加到环境变量中,或者将两个文件复制到被包含在环境变量中的目录(/usr/bin, /usr/local/bin, 或 /bin)下的方式来避免这一问题。当MMS-EASE Lite应用程序出现异常使slistendd任务不能终止时,有时必须使用kill命令手动终止sreadd。

TCP/IP移植

唯一需要移植的是操作系统提供的TCP/IP接口。这段代码已经移植到了一些操作系统中。如果目标操作系统提供以下服务,那么移植移植过程是比较简单的。

TCP/IP的socket接口
多任务和多线程功能
pipe功能

对于大多数移植,应该只需要修改以下四个模块。它们都位于目录\mmslite\uca\leant下:

tp0_sock.c (通用socket代码,Main任务的一部分)
tp0_list.c (Listen任务,适用于UNIX & Win32环境)
tp0_read.c (Read任务,适用于UNIX & Win32环境)
tp0_unix.c (UNIX专用代码,Main任务的一部分)
tp0_w32.c (Win32专用代码,Main任务的一部分)

编译选项

所有用于TCP/IP相关代码都被编译到ositcpe.lib库文件中(Linux系统中为ositcpe.a)。为了在下列库中使能正确的代码,必须使能以下编译选项。

-D LEAN_T
-D MOSI
-D TP0_ENABLED

以下为可选编译选项:

-D S_MT_SUPPORT 使用多线程

3 协议栈配置(Protocol Stack Configuration )

3.1 TCP/IP配置

用户必须填写以下全局结构来配置TCP/IP (viaRFC1006)堆栈:

TP0_CFG tp0_cfg;

其中TP0_CFG的定义如下:

typedef struct
 {
 ST_UINT16 max_tpdu_len; /* max len of TPDU. */
 /* Use to allocate TPDU buffers. */
 ST_UCHAR max_tpdu_len_enc; /* Binary encoded MAX TPDU len. Computed*/
 /* from max_tpdu_len by tp0_initialize. */
 ST_UCHAR max_num_conns; /* Max # Connections */
 ST_BOOLEAN keepalive; /* Use KEEPALIVE option on Sockets. */
 } TP0_CFG; /* For TP0/RFC1006 only. */ 

在调用tp0_initialize之前,用户必须设置每个参数。如果在tp0_initialize被调用之后修改该结构体,则修改无效。以下模块提供了一个硬编码的示例:

tp4_hc.c 

也可以用osicfg.xml文件来配置。请参阅章节“常用配置问题”。

3.2 OSI传输层(TP4)配置

配置TP4 API时,用户需要填写以下全局结构:

TP_CFG tp_cfg;

其中 TP_CFG的定义如下:

typedef struct
 {
 ST_UINT16 max_tpdu_len; /* max len of TPDU. Base on SNPDU size. */
 /* Use to allocate TPDU buffers. */
 ST_UCHAR max_tpdu_len_enc; /* Binary encoded MAX TPDU len. Computed*/
 /* from max_tpdu_len by tp4_initialize. */
 ST_UCHAR max_rem_cdt; /* Max credits we can handle. */
 /* Will allocate this many TPDU_DT */
 /* structs. */
 /* CRITICAL: MUST BE POWER OF 2. */
 ST_UCHAR loc_cdt; /* CDT value we will ALWAYS send in ACK */
 /* We only accept in-sequence TPDUs so */
 /* only purpose of this is to */
 /* allow peer to send ahead. */
 ST_UCHAR max_spdu_outst;
 /* Max # of SPDUs outstanding per conn. */
 /* Will allocate this many SPDU_INFO */
 /* structs for transmit queue. */
 /* CRITICAL: MUST BE POWER OF 2. */
 ST_UCHAR max_num_conns; /* Max # Connections */ 
 ST_UINT16 window_time; /* Window Time */
 ST_UINT16 inact_time; /* Inactivity Time */
 ST_UINT16 retrans_time; /* Retransmission Time */
 ST_UCHAR max_trans; /* Max # of transmissions of a TPDU */
 ST_UCHAR ak_delay; /* # of loops to delay sending AK. */
 } TP_CFG; 

在调用tp4_initialize之前,用户必须设置每个参数。如果在tp4_initialize被调用之后修改该结构体,则修改无效。以下模块提供了一个硬编码的示例:

Tp4_hc.c

也可以用osicfg.xml文件来配置。请参阅章节“常用配置问题”。

3.3 OSI网络层(CLNP/ES-IS)配置

配置OSI网络层API时,用户需要填写以下全局结构:

CLNP_PARAM clnp_param;

其中 CLNP_PARAM的定义如下:

typedef struct
 {
 ST_UCHAR pdu_lifetime;
 /* PDU lifetime (in 500 msec units) for */
 /* outgoing DT PDUs. */
 /* init to CLNP_DEF_PDU_LIFETIME */
 ST_UCHAR pdu_lifetime_dec;
 /* PDU lifetime decrement (1=500msec) */
 /* for incoming DT or ER PDUs. */
 /* init to CLNP_DEF_PDU_LIFETIME_DEC */
 ST_UINT16 esh_cfg_timer;
 /* How often we report our presence to */
 /* other network entities (in seconds) */
 /* init to CLNP_DEF_ESH_CFG_TIMER */
 ST_UINT16 esh_delay; /* Delay time before first ESH is sent */
 /* init to CLNP_DEF_ESH_DELAY */
 ST_UCHAR loc_mac [CLNP_MAX_LEN_MAC];
 /* Local MAC address */
 /* For ADLC the NS-USER sets the loc_mac*/
 /* DEBUG: Now the loc_mac has to match */
 /* the address in adlc.cfg !!! */
 /* For the Ethernet this param will be */
 /* read from the driver during init */

ST_UCHAR loc_nsap [1+CLNP_MAX_LEN_NSAP];
/* Local len & NSAP address */
 }CLNP_PARAM; 

在调用clnp_init之前,用户必须设置每个参数。如果在clnp_init被调用之后修改该结构体,则修改无效。以下模块提供了一个硬编码的示例:

clnp_hc.c 

也可以用osicfg.xml文件来配置。请参阅章节“常用配置问题”。

3.4 网络地址

MMS-EASE Lite中定义了“应用程序引用名称(Application Reference Name)”,缩写为“AR Name”。AR Name是一个最大长度为32字符的ASCII字符串,它用于共同确定应用程序实体信息(AP标题与AE限定符)以及与应用程序相关联的演示地址。换句话说,AR Name并不是两个应用程序通过网络实际进行交换的东西,而是一种更具可读性的ACSE和寻址信息的简写。MMS-EASE Lite应用程序使用AR Name调用MMS连接管理API。

要配置网络地址,用户必须设置以下全局指针来指向DIB_ENTRY数组结构:

DIB_ENTRY *loc_dib_table; 本地地址 (至少有一个)
DIB_ENTRY *rem_dib_table; 远程地址 (根据需求配置)
typedef struct
 {
 ST_LONG reserved; /* reserved field */
 ST_CHAR *name; /* AR Name */
 ST_CHAR local; /* SD_TRUE if local, SD_FALSE if remote*/
 ST_UCHAR AP_title_pres; /* present flag */
 MMS_OBJ_ID AP_title; /* AP title */
 ST_UCHAR AP_inv_id_pres; /* present flag */
 ST_INT32 AP_invoke_id; /* AP invocation ID */
 ST_UCHAR AE_qual_pres; /* present flag */
 ST_INT32 AE_qual; /* AE qualifier */
 ST_UCHAR AE_inv_id_pres; /* present flag */
 ST_INT32 AE_invoke_id; /* AE invocation ID */
 PRES_ADDR pres_addr; /* Presentation address. */
 } DIB_ENTRY; 

这个DIB_ENTRY定义引用了以下的PRES_ADDR结构:

typedef struct tagPRES_ADDR
 {
 int psel_len;
 char psel [MAX_PSEL_LEN];
 int ssel_len;
 char ssel [MAX_SSEL_LEN];
 ST_INT tp_type; /* Transport Type: TP_TYPE_TP4, */
 /* TP_TYPE_TCP, or TP_TYPE_TPX. */
 int tsel_len;
 char tsel [MAX_TSEL_LEN];
 int nsap_len;
 char nsap [MAX_IP_ADDR_LEN]; /* If TP_TYPE_TP4, contains NSAP. */
/* If TP_TYPE_TCP, contains IP addr. */
/* Only used for “remote” addresses. */

 } PRES_ADDR; 

注:根据现有的OSI协议,PSEL、SSEL和TSEL参数都被更改为最大4字节,这提高了MMS-EASE Lite的内存使用。该标准建议如下:

PSEL 4 - International Standard Profiles
SSEL 2 - GOSIP Ver2
TSEL 2 - GOSIP Ver2

传输类型TP_TYPE_TPX只能用于“本地”入口。这表示同时支持TP4和TCP。指针loc_dib_table和rem_dib_table的设置可以以任何适合目标平台的方式进行。以下示例代码模块提供了硬编码的示例(只有定义了HARD_CODED_CFG时才执行代码):

server.c
client.c 

也可以使用osicfg.xml文件进行配置,详情可以参考以下内容。

3.5 使用XML输入文件进行协议栈配置

以下模块提供了使用Sisco通用配置实用程序来配置TP4 API, CLNP API以及DIB条目的示例:

osicfgx.c

这段代码处理以下配置文件:

osicfg.xml

配置文件osicfg.xml分为TP4、TCP、CLNP和DIB表项(网络地址)四个部分。由于篇幅所限,本文档不会详细描述该配置文件文件和Sisco通用配置实用程序的使用方法。

3.6 ACSE身份验证

以下内容描述了ISO/IEC 8650-1附录B中的ACSE认证相关内容。

acseauth.h文件包含了传递给用户和ASN.1解析器的身份验证结构体ACSE_AUTH_INFO。

如果不希望进行ACSE认证,发起调用的节点可以利用mvla_initiate_req服务向被调用节点发送一个初始化请求PDU。如果需要进行ACSE身份验证,ACSE用户必须调用mvla_initiate_req_ex,并传递一个指向ACSE_AUTH_INFO结构的指针,该结构包含他们希望发送给被调用节点的身份验证信息。身份验证信息的编码是按照ACSE规范进行的,在acse2enc.c中完成。

被调用侧将接收带有身份验证的请求PDU,并在acse2dec.c中对其进行解码。填写ACSE_AUTH_INFO结构,并通过u_mvl_connect_ind_ex传递给用户。用户既可以接受身份验证并返回成功,也可以拒绝身份验证并向发起调用的节点发送一个放弃验证的PDU。拒绝身份验证的原因被定义在acseauth.h的部分常量中,并且编码在放弃验证PDU中。

此外,一个指向响应身份验证结构体的指针将会发送给用户,该结构体在连接确认期间可能被发送回调用节点。在关联请求和关联响应APDUs中使用这种交换身份验证信息的方法可以提供双向身份验证。

如果调用方确实在AARE APDU中接收到身份验证,则该信息将在u_mvl_connect_cnf_ex中传递给用户。同样,这个函数可能返回成功或错误诊断,返回信息将会被编码中放弃验证PDU发送。

身份验证值本身在ACSE规范中定义。ACSE_AUTH_INFO结构可以使用密码机制(如ACSE规范中定义的那样)或其他机制。在“other”机制的情况下,用户需要处理认证值的ASN.1解码和编码。此外,cisco还可以提供基于证书的ACSE认证机制。

ACSE认证编码/解码被编译成MMS-EASE Lite库代码。关于ACSE认证示例代码,请参阅\mmslite\mvl\usr目录中提供的客户端、服务器或uca_srvr示例。