170 lines
11 KiB
C
170 lines
11 KiB
C
/*
|
||
* 101/104共用ASDU容器。公共头、VSQ/SQ、容量、IOA布局由本层维护,
|
||
* 数值、品质、命令限定词和时标的编解码由具体信息对象完成。
|
||
* 参数与字节缓冲区均由调用方提供;无动态内存或可变全局状态。
|
||
*
|
||
* 发送:具体类型Create -> Asdu_Create -> Add_InformationObject(逐个)-> Get_Data。
|
||
* 接收:Asdu_Parse -> 按info.type_id选择具体结构 -> Get_Element。
|
||
* Type ID决定载荷格式,COT表示传送原因,CA是站公共地址,IOA是点/对象地址;
|
||
* VSQ低7位是对象数,bit7是SQ。SQ=1只保存首IOA,随后各对象地址依次加1。
|
||
*/
|
||
#ifndef IEC60870_ASDU_H
|
||
#define IEC60870_ASDU_H
|
||
|
||
#include "IEC60870_Codec.h"
|
||
#include "IEC60870_InformationObjects.h"
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* COT首字节的低6位,不含独立的P/N和TEST标志;不是完整的COT适用规则表。 */
|
||
typedef enum {
|
||
IEC60870_ASDU_COT_PERIODIC = 1, /* 周期/循环上报。 */
|
||
IEC60870_ASDU_COT_BACKGROUND = 2, /* 背景扫描。 */
|
||
IEC60870_ASDU_COT_SPONTANEOUS = 3, /* 突发上报。 */
|
||
IEC60870_ASDU_COT_INITIALIZED = 4, /* 初始化结束。 */
|
||
IEC60870_ASDU_COT_REQUEST = 5, /* 请求或被请求的数据。 */
|
||
IEC60870_ASDU_COT_ACTIVATION = 6, /* 激活请求。 */
|
||
IEC60870_ASDU_COT_ACTIVATION_CON = 7, /* 激活确认,P/N另行指定。 */
|
||
IEC60870_ASDU_COT_DEACTIVATION = 8, /* 停止激活请求。 */
|
||
IEC60870_ASDU_COT_DEACTIVATION_CON = 9, /* 停止激活确认。 */
|
||
IEC60870_ASDU_COT_ACTIVATION_TERMINATION = 10, /* 激活终止。 */
|
||
IEC60870_ASDU_COT_RETURN_INFO_REMOTE = 11, /* 远方命令引起的返回信息。 */
|
||
IEC60870_ASDU_COT_RETURN_INFO_LOCAL = 12, /* 当地命令引起的返回信息。 */
|
||
IEC60870_ASDU_COT_FILE_TRANSFER = 13, /* 文件传输。 */
|
||
IEC60870_ASDU_COT_INTERROGATED_BY_STATION = 20, /* 响应站总召唤的数据。 */
|
||
IEC60870_ASDU_COT_COUNTER_INTERROGATION = 37, /* 响应电能脉冲总召唤的数据。 */
|
||
IEC60870_ASDU_COT_COUNTER_GROUP_1 = 38,
|
||
IEC60870_ASDU_COT_COUNTER_GROUP_2 = 39,
|
||
IEC60870_ASDU_COT_COUNTER_GROUP_3 = 40,
|
||
IEC60870_ASDU_COT_COUNTER_GROUP_4 = 41,
|
||
IEC60870_ASDU_COT_UNKNOWN_TYPE = 44, /* 未知类型标识。 */
|
||
IEC60870_ASDU_COT_UNKNOWN_CAUSE = 45, /* 未知传送原因。 */
|
||
IEC60870_ASDU_COT_UNKNOWN_COMMON_ADDRESS = 46, /* 未知公共地址。 */
|
||
IEC60870_ASDU_COT_UNKNOWN_IOA = 47 /* 未知信息对象地址。 */
|
||
} IEC60870_AsduCause_t;
|
||
|
||
/* 创建ASDU时仅提供公共头;Type ID由首个成功添加的对象决定,VSQ由容器维护。
|
||
* 字段赋值前不得截窄越界整数。COT/CA/IOA的业务适用性由应用层检查。 */
|
||
typedef struct {
|
||
uint8_t cause_of_transmission; /* COT低6位,0..63,不含P/N及TEST。 */
|
||
uint8_t is_negative_confirm; /* P/N:0肯定、1否定。 */
|
||
uint8_t is_test; /* TEST:0/1。 */
|
||
uint8_t originator_address; /* 显式OA;COT为1字节时忽略,解析输出0。 */
|
||
uint16_t common_address; /* 1字节CA拒绝超过255的值,不截断。 */
|
||
} IEC60870_AsduHeader_t;
|
||
|
||
/* 调用方保存的容器;控制字段由Create/Create_From_Buffer初始化,不得手工修改。
|
||
* 字节区通过Add或字段设置接口修改;parameters在使用期间必须有效且不变。
|
||
* Create_From_Buffer借用可写原文,只允许设置公共头,不允许继续追加对象。
|
||
* 同一实例由调用方串行使用;对象不分配或释放其借用的缓冲区。 */
|
||
typedef struct {
|
||
uint8_t *buffer; /* ASDU原文,不含APCI。 */
|
||
uint16_t capacity; /* min(调用方容量, maxSizeOfASDU)。 */
|
||
uint16_t length; /* 当前字节数,空容器仅有公共头。 */
|
||
uint8_t type_id; /* 空容器为0,首个对象成功加入后固定。 */
|
||
uint8_t object_count; /* 按类型限制为0..127或0..1;借用时记录原VSQ数量。 */
|
||
uint8_t is_sequence; /* SQ:0逐对象地址,1连续地址。 */
|
||
uint8_t is_from_buffer;/* 1为借用收到的原文;0为Create创建的构造容器。 */
|
||
uint32_t first_ioa; /* 构造时供SQ连续性校验;借用原文时为0且不使用。 */
|
||
const IEC60870_AppLayerParameters_t *parameters; /* 借用参数,不复制。 */
|
||
} IEC60870_Asdu_t;
|
||
|
||
/* 结果须显式比较IEC60870_ASDU_OK;FULL=0,ERROR/UNSUPPORTED为负值。
|
||
* 所有失败保留调用方输出。结构体、输出参数和字节区不得互相重叠;
|
||
* 唯一例外是Build_Response允许输入/输出字节区重叠。 */
|
||
|
||
/* 创建空容器并固定参数、SQ和公共头;参数必须非空,头长=2+COT宽度+CA宽度。
|
||
* 有效容量不足头长返回FULL;SQ=1只适用于监视信息和文件目录。
|
||
* 初始Type ID为0,首个对象失败不会改变类型;空容器不能Get_Data。 */
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Create(IEC60870_Asdu_t *self,
|
||
const IEC60870_AppLayerParameters_t *parameters,
|
||
uint8_t *buffer, uint16_t capacity, const IEC60870_AsduHeader_t *header, int is_sequence);
|
||
|
||
/**
|
||
* 功能:在调用方存储中建立可修改的ASDU对象,直接借用已有报文字节。
|
||
* 输入参数:parameters——对应原文布局的非空合法参数,使用期间有效且不变。
|
||
* buffer——非空可写ASDU原文,不含APCI;length——原文完整长度。
|
||
* 输出参数:self——成功时写入容器,失败保持原值;buffer不变。
|
||
* 返回值:OK为成功,ERROR为参数或最低长度非法。
|
||
* 说明:只检查公共头和已知类型最低长度,允许未知类型回显错误应答。
|
||
* 业务执行前仍应调用Parse进行完整校验;不复制、不分配、不追加对象。
|
||
* 字段设置会修改buffer;必须保证buffer和parameters在使用期间有效。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Create_From_Buffer(IEC60870_Asdu_t *self,
|
||
const IEC60870_AppLayerParameters_t *parameters, uint8_t *buffer, uint16_t length);
|
||
|
||
/**
|
||
* 功能:原地设置ASDU的传送原因。
|
||
* 输入参数:self——合法构造容器或借用原文的对象;cause——0..63。
|
||
* 输出参数:self->buffer——仅更新COT低6位,保留P/N、TEST及其余字节。
|
||
* 返回值:OK为成功,ERROR为参数非法;失败不修改原文。
|
||
* 说明:不判断传送原因的业务适用性,也不发送报文。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Set_COT(IEC60870_Asdu_t *self, int cause);
|
||
|
||
/**
|
||
* 功能:原地设置ASDU的肯定或否定确认标志。
|
||
* 输入参数:self——合法ASDU对象;is_negative——0肯定、1否定。
|
||
* 输出参数:self->buffer——仅更新P/N位,保留COT、TEST及其余字节。
|
||
* 返回值:OK为成功,ERROR为参数非法;失败不修改原文。
|
||
* 说明:设置字段不代表业务已接受命令,也不提交发送。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Set_Negative(IEC60870_Asdu_t *self, int is_negative);
|
||
|
||
/**
|
||
* 功能:原地设置ASDU公共地址,适用于合法构造容器或借用可写原文的对象。
|
||
* 输入参数:self——合法ASDU对象;common_address——0..65535,须适合配置的CA宽度。
|
||
* 输出参数:self->buffer——仅更新CA字段,按小端序写入1或2字节。
|
||
* 返回值:OK为成功,ERROR为对象状态或字段宽度非法;失败不修改任何原文字节。
|
||
* 说明:偏移由COT宽度确定,保留OA、TEST、P/N及对象原文;不限制业务上的站地址范围。
|
||
* 不分配、不改变容器长度、不发送;原文修改后,旧解析视图须重新解析。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Set_CA(IEC60870_Asdu_t *self, uint16_t common_address);
|
||
|
||
/* 通过对象函数表编码并复制到ASDU,成功后不再借用对象本身。
|
||
* 单类型约束、SQ连续地址和IOA宽度统一检查。字节容量不足、对象达到127个,
|
||
* 或单对象类型已加入对象时返回FULL;类型不匹配、SQ或字段非法返回ERROR。
|
||
* FULL时由调用方提交当前非空ASDU,另起一条重试同一对象,不自动发送或拆包。 */
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Add_InformationObject(IEC60870_Asdu_t *self,
|
||
const IEC60870_InformationObject_t *object);
|
||
|
||
/* 获取构造容器或Create_From_Buffer对象的借用原文及当前长度。
|
||
* Create创建的空容器不可导出;借用原文对象沿用公共头最低长度检查。
|
||
* 不分配、不复制、不发送;需完整检查对象值时先调用Parse。
|
||
* 缓冲区修改后原视图失效;提交104传输时应使用2/2/3配置及<=249的总长。 */
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Get_Data(const IEC60870_Asdu_t *self,
|
||
const uint8_t **asdu, uint16_t *length);
|
||
|
||
/* 统一解析67种监视、命令、初始化结束、参数和文件类型。
|
||
* 验证精确长度和所有对象,文件段按LOS检查变长载荷。
|
||
* 监视与文件目录支持SQ=0/1;参数允许多个对象但要求SQ=0。
|
||
* 其余类型要求SQ=0、数量=1;未知类型返回UNSUPPORTED。
|
||
* info借用参数和输入原文,二者在取对象期间均须有效且不变。 */
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Parse(const IEC60870_AppLayerParameters_t *parameters,
|
||
const uint8_t *asdu, uint16_t length, IEC60870_AsduInfo_t *info);
|
||
|
||
/* 按Type ID调用具体解析器,向对应类型的存储写入一个按值对象。
|
||
* 例如Type 1传入&point、sizeof(point),其中point为IEC60870_SinglePointInformation_t。
|
||
* 调用方必须依据info->type_id选择正确的结构类型;object_size至少为该结构尺寸,
|
||
* 尺寸检查不能替代C语言中的类型匹配。index从0开始,输出无需预先Create。
|
||
* 对象不借用原文;公共头继续从info读取,选中对象之外不再次重复解码。 */
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Get_Element(const IEC60870_AsduInfo_t *info,
|
||
uint8_t index, void *object, size_t object_size);
|
||
|
||
/* 复制请求,只更新COT及P/N,保留TEST、OA、CA、VSQ和对象原文。
|
||
* cause接受7/9/10/44..47,is_negative为0/1;否定激活确认传7、1。
|
||
* 仅检查公共头及最低长度,允许未知类型回显,不要求对象解析成功。
|
||
* 参数须对应请求布局。容量不足返回FULL,所有失败保留out及out_length。
|
||
* 输入/输出字节区支持原地与部分重叠;不执行命令或自动发送。 */
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Build_Response(const IEC60870_AppLayerParameters_t *parameters,
|
||
const uint8_t *request, uint16_t length,
|
||
int cause, int is_negative, uint8_t *out, uint16_t capacity, uint16_t *out_length);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* IEC60870_ASDU_H */
|