462 lines
24 KiB
C
462 lines
24 KiB
C
/*
|
||
* ASDU层仅处理公共头、对象布局、容量和分派。
|
||
* 对象编码调用基类函数表;对象解码按Type ID调用独立类型解析器。
|
||
* 所有外部可见写入都在字段、布局和容量验证完成后提交,失败不留下半条报文。
|
||
*/
|
||
#include "IEC60870_Asdu.h"
|
||
#include "IEC60870_InformationObjects_Internal.h"
|
||
|
||
#include <string.h>
|
||
|
||
/**
|
||
* 功能:计算当前配置的ASDU公共头字节数。
|
||
* 输入参数:parameters——已通过IEC60870_AppLayerParameters_Valid检查的非空参数。
|
||
* 输出参数:无。
|
||
* 返回值:2+sizeOfCOT+sizeOfCA,即4、5或6字节。
|
||
* 说明:固定的2字节是Type ID和VSQ;OA已包含在2字节COT中,不能再次累加。
|
||
*/
|
||
static uint16_t Header_Size(const IEC60870_AppLayerParameters_t *parameters)
|
||
{
|
||
return (uint16_t)(2 + parameters->sizeOfCOT + parameters->sizeOfCA);
|
||
}
|
||
|
||
/**
|
||
* 功能:检查信息对象区的精确长度、数量、SQ约束及连续地址上限。
|
||
* 输入参数:info——非空公共头视图,参数和对象原文在使用期间必须有效且不变。
|
||
* type——与info->type_id匹配的非空只读类型描述。
|
||
* 输出参数:无。
|
||
* 返回值:1表示对象区布局合法;0表示参数、数量、长度或隐含地址范围非法。
|
||
* 说明:先检查长度,再读取首IOA;这里只验证布局,不检查值、品质或时标。
|
||
*/
|
||
static int Layout_Valid(const IEC60870_AsduInfo_t *info,
|
||
const IEC60870_InformationObjectVTable *type)
|
||
{
|
||
uint16_t expected;
|
||
if (!IEC60870_AppLayerParameters_Valid(info->parameters) ||
|
||
info->information_objects == NULL || info->object_count == 0U ||
|
||
info->object_count > IEC60870_ASDU_MAX_OBJECTS || info->is_sequence > 1U ||
|
||
(info->is_sequence && !type->supports_sequence) ||
|
||
(type->single_object && info->object_count != 1U)) {
|
||
return 0;
|
||
}
|
||
/* 设N为数量、D为值载荷长度、L为IOA宽度:SQ=0占N*(L+D),SQ=1占L+N*D。
|
||
* 精确相等同时拒绝截短对象和额外尾字节;这里不计ASDU公共头。 */
|
||
expected = (uint16_t)(info->object_count * type->payload_size +
|
||
(info->is_sequence ? info->parameters->sizeOfIOA :
|
||
info->object_count * info->parameters->sizeOfIOA));
|
||
if (type->get_payload_size_from_buffer != NULL) {
|
||
uint16_t payload_size;
|
||
uint16_t address_size = (uint16_t)info->parameters->sizeOfIOA;
|
||
/* 当前变长类型是单对象文件段;先保护段头读取,再依据LOS计算实际长度。 */
|
||
if (info->object_count != 1U || info->is_sequence ||
|
||
info->information_objects_len < address_size) { return 0; }
|
||
payload_size = type->get_payload_size_from_buffer(info->information_objects + address_size,
|
||
(uint16_t)(info->information_objects_len - address_size));
|
||
if (payload_size == 0U) { return 0; }
|
||
expected = (uint16_t)(address_size + payload_size);
|
||
}
|
||
if (expected != info->information_objects_len ||
|
||
expected > info->parameters->maxSizeOfASDU - Header_Size(info->parameters)) {
|
||
return 0;
|
||
}
|
||
/* SQ首地址本身可表示,但末地址首IOA+N-1也必须能由同样的IOA宽度表示。 */
|
||
return !info->is_sequence ||
|
||
IEC60870_Object_Read_LE(info->information_objects, (unsigned int)info->parameters->sizeOfIOA) <=
|
||
IEC60870_Object_Max_IOA(info->parameters) - (info->object_count - 1U);
|
||
}
|
||
|
||
/**
|
||
* 功能:定位指定对象的值载荷,同时恢复其信息对象地址。
|
||
* 输入参数:info——已通过Layout_Valid校验的非空ASDU视图。
|
||
* type——与info->type_id匹配的非空只读类型描述。
|
||
* index——从0开始的索引,调用方必须保证小于object_count。
|
||
* 输出参数:ioa——非空地址输出;SQ=1时使用首地址加索引还原。
|
||
* payload_length——非空输出,返回固定或变长载荷的实际长度。
|
||
* 返回值:输入原文中的载荷借用指针,不含IOA,不复制字节。
|
||
* 说明:本内部函数不再验证长度/索引;返回指针的有效期与原ASDU一致。
|
||
*/
|
||
static const uint8_t *Element_Position(const IEC60870_AsduInfo_t *info,
|
||
const IEC60870_InformationObjectVTable *type, uint8_t index, uint32_t *ioa,
|
||
uint16_t *payload_length)
|
||
{
|
||
unsigned int address_size = (unsigned int)info->parameters->sizeOfIOA;
|
||
const uint8_t *data = info->information_objects;
|
||
*payload_length = type->payload_size;
|
||
if (type->get_payload_size_from_buffer != NULL) {
|
||
/* Layout_Valid已确认变长对象为SQ=0、数量1且载荷完整。 */
|
||
*payload_length = type->get_payload_size_from_buffer(data + address_size,
|
||
(uint16_t)(info->information_objects_len - address_size));
|
||
}
|
||
/* SQ=1的对象区为:首IOA、载荷0、载荷1……;后续地址不占报文字节。 */
|
||
if (info->is_sequence) {
|
||
*ioa = IEC60870_Object_Read_LE(data, address_size) + index;
|
||
return data + address_size + index * type->payload_size;
|
||
}
|
||
/* SQ=0每个对象都有独立IOA,故步长包含地址;取值前先跳过该地址。 */
|
||
data += index * (address_size + type->payload_size);
|
||
*ioa = IEC60870_Object_Read_LE(data, address_size);
|
||
return data + address_size;
|
||
}
|
||
|
||
/**
|
||
* 功能:核对容器控制字段与已构造ASDU布局是否一致。
|
||
* 输入参数:self——待检查容器或借用原文对象,可为NULL。
|
||
* 输出参数:无。
|
||
* 返回值:1表示结构状态一致;0表示参数、容量、类型、数量、长度或首地址非法。
|
||
* 说明:构造容器按类型重算精确长度;借用原文对象只检查公共头及最低长度。
|
||
* 不重新解码值,参数、控制字段及对象区须由调用方保持有效且不变。
|
||
*/
|
||
static int Container_Valid(const IEC60870_Asdu_t *self)
|
||
{
|
||
uint16_t expected;
|
||
const IEC60870_InformationObjectVTable *type;
|
||
if (self == NULL || self->buffer == NULL ||
|
||
!IEC60870_AppLayerParameters_Valid(self->parameters) ||
|
||
self->capacity < Header_Size(self->parameters) ||
|
||
self->capacity > self->parameters->maxSizeOfASDU ||
|
||
self->length < Header_Size(self->parameters) ||
|
||
self->length > self->capacity || self->is_sequence > 1U ||
|
||
self->object_count > IEC60870_ASDU_MAX_OBJECTS || self->is_from_buffer > 1U) {
|
||
return 0;
|
||
}
|
||
if (self->is_from_buffer) {
|
||
IEC60870_AsduInfo_t info;
|
||
/* 已有原文的容量固定为其长度,不将未知对象当作可追加的构造容器。
|
||
* 不读取未知类型的IOA;first_ioa仅供构造时的连续地址检查使用。 */
|
||
return self->capacity == self->length && self->first_ioa == 0U &&
|
||
IEC60870_Parse_ASDU_Header(self->parameters, self->buffer, self->length, &info) &&
|
||
self->type_id == info.type_id && self->object_count == info.object_count &&
|
||
self->is_sequence == info.is_sequence;
|
||
}
|
||
expected = Header_Size(self->parameters);
|
||
if (self->object_count == 0U) {
|
||
if (self->type_id != 0U) { return 0; }
|
||
} else {
|
||
type = IEC60870_InformationObject_Get_Descriptor(self->type_id);
|
||
if (type == NULL || (self->is_sequence && !type->supports_sequence) ||
|
||
(type->single_object && self->object_count != 1U)) {
|
||
return 0;
|
||
}
|
||
expected = (uint16_t)(expected + self->object_count * type->payload_size +
|
||
(self->is_sequence ? self->parameters->sizeOfIOA :
|
||
self->object_count * self->parameters->sizeOfIOA));
|
||
if (type->get_payload_size_from_buffer != NULL) {
|
||
IEC60870_AsduInfo_t info;
|
||
if (!IEC60870_Parse_ASDU_Header(self->parameters, self->buffer, self->length, &info) ||
|
||
!Layout_Valid(&info, type)) { return 0; }
|
||
expected = self->length;
|
||
}
|
||
}
|
||
if (self->length != expected || self->buffer[0] != self->type_id ||
|
||
self->buffer[1] != (uint8_t)(self->object_count | (self->is_sequence << 7))) {
|
||
return 0;
|
||
}
|
||
if (self->object_count != 0U &&
|
||
(self->first_ioa != IEC60870_Object_Read_LE(self->buffer + Header_Size(self->parameters),
|
||
(unsigned int)self->parameters->sizeOfIOA) ||
|
||
(self->is_sequence && self->first_ioa >
|
||
IEC60870_Object_Max_IOA(self->parameters) - (self->object_count - 1U)))) {
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/**
|
||
* 功能:在调用方缓冲区中创建一个尚未加入对象的ASDU。
|
||
* 输入参数:parameters——非空合法参数;容器借用指针,使用期间必须有效且不变。
|
||
* capacity——buffer实际可用字节数;有效容量取此值与maxSizeOfASDU的较小值。
|
||
* header——非空公共头配置;COT为0..63,P/N和TEST为0/1,CA须适合所选宽度。
|
||
* is_sequence——0为逐对象IOA,1为连续地址;一条ASDU内不自动切换。
|
||
* 输出参数:self——非空容器存储,成功后记录缓冲区、参数及初始状态。
|
||
* buffer——非空可写ASDU缓冲区,初始只写公共头,不含APCI。
|
||
* 返回值:OK为成功,FULL为实际缓冲区不足头长,ERROR为参数或字段非法。
|
||
* 说明:全部检查通过后才写回;首对象尚未确定,Type ID和对象数均为0。
|
||
* 本函数不分配内存,也不发送;SQ适用性按具体类型检查,空容器不能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)
|
||
{
|
||
IEC60870_Asdu_t result;
|
||
uint16_t header_length;
|
||
if (self == NULL || buffer == NULL || header == NULL ||
|
||
!IEC60870_AppLayerParameters_Valid(parameters) ||
|
||
(is_sequence != 0 && is_sequence != 1) ||
|
||
header->cause_of_transmission > 63U || header->is_negative_confirm > 1U ||
|
||
header->is_test > 1U || (parameters->sizeOfCA == 1 && header->common_address > 255U)) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
header_length = Header_Size(parameters);
|
||
if (capacity < header_length) { return IEC60870_ASDU_FULL; }
|
||
memset(&result, 0, sizeof(result));
|
||
result.buffer = buffer;
|
||
/* 缓冲区较大也不能突破协议配置上限;仅保存参数指针,不复制配置。 */
|
||
result.capacity = capacity > parameters->maxSizeOfASDU ?
|
||
(uint16_t)parameters->maxSizeOfASDU : capacity;
|
||
result.length = header_length;
|
||
result.is_sequence = (uint8_t)is_sequence;
|
||
result.parameters = parameters;
|
||
/* 此后没有可能失败的校验;按所选宽度恰好写入header_length字节。 */
|
||
/* Type ID暂置0;VSQ bit7为SQ、低7位为数量,初始数量为0。 */
|
||
buffer[0] = 0;
|
||
buffer[1] = (uint8_t)(is_sequence << 7);
|
||
/* COT首字节:bit0..5为原因、bit6为P/N、bit7为TEST;2字节COT才写OA。 */
|
||
buffer[2] = (uint8_t)(header->cause_of_transmission |
|
||
(header->is_negative_confirm << 6) | (header->is_test << 7));
|
||
if (parameters->sizeOfCOT == 2) { buffer[3] = header->originator_address; }
|
||
IEC60870_Object_Write_LE(buffer + 2 + parameters->sizeOfCOT,
|
||
header->common_address, (unsigned int)parameters->sizeOfCA);
|
||
*self = result;
|
||
return IEC60870_ASDU_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:将已有可写ASDU原文包装成调用方持有的对象。
|
||
* 输入参数:parameters——非空应用层参数;buffer——可写原文;length——原文长度。
|
||
* 输出参数:self——全部检查成功后写入;buffer不变。
|
||
* 返回值:OK为成功,ERROR为参数或最低长度非法。
|
||
* 说明:借用参数和原文,不分配、不复制;本入口不替代业务使用的完整Parse。
|
||
* 容量固定为原文长度,后续只允许修改公共头,不能追加信息对象。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Create_From_Buffer(IEC60870_Asdu_t *self,
|
||
const IEC60870_AppLayerParameters_t *parameters, uint8_t *buffer, uint16_t length)
|
||
{
|
||
IEC60870_AsduInfo_t info;
|
||
IEC60870_Asdu_t result;
|
||
if (self == NULL || !IEC60870_Parse_ASDU_Header(parameters, buffer, length, &info)) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
memset(&result, 0, sizeof(result));
|
||
result.buffer = buffer;
|
||
result.capacity = length;
|
||
result.length = length;
|
||
result.type_id = info.type_id;
|
||
result.object_count = info.object_count;
|
||
result.is_sequence = info.is_sequence;
|
||
result.is_from_buffer = 1U;
|
||
result.parameters = parameters;
|
||
*self = result;
|
||
return IEC60870_ASDU_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:修改ASDU传送原因,保留COT字节中的两个独立标志。
|
||
* 输入参数:self——合法ASDU对象;cause——0..63。
|
||
* 输出参数:self->buffer[2]——校验成功后更新低6位。
|
||
* 返回值:OK为成功,ERROR为参数或对象状态非法。
|
||
* 说明:所有字段宽度配置中COT首字节均位于偏移2;不改变OA或载荷。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Set_COT(IEC60870_Asdu_t *self, int cause)
|
||
{
|
||
if (cause < 0 || cause > 63 || !Container_Valid(self)) { return IEC60870_ASDU_ERROR; }
|
||
self->buffer[2] = (uint8_t)((self->buffer[2] & 0xC0U) | (uint8_t)cause);
|
||
return IEC60870_ASDU_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:修改ASDU的P/N标志,保留传送原因及TEST。
|
||
* 输入参数:self——合法ASDU对象;is_negative——0或1。
|
||
* 输出参数:self->buffer[2]——校验成功后仅更新bit6。
|
||
* 返回值:OK为成功,ERROR为参数或对象状态非法。
|
||
* 说明:不执行命令,不提交发送;失败保持字节原值。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Set_Negative(IEC60870_Asdu_t *self, int is_negative)
|
||
{
|
||
if ((is_negative != 0 && is_negative != 1) || !Container_Valid(self)) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
self->buffer[2] = (uint8_t)((self->buffer[2] & 0xBFU) | (uint8_t)(is_negative << 6));
|
||
return IEC60870_ASDU_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:修改ASDU公共地址,保留其他公共头字段与对象区。
|
||
* 输入参数:self——合法ASDU对象;common_address——须适合sizeOfCA的无符号地址。
|
||
* 输出参数:self->buffer——在2+sizeOfCOT偏移按小端写入sizeOfCA字节。
|
||
* 返回值:OK为成功,ERROR为非法容器或1字节CA无法表示的地址;失败不写入。
|
||
* 说明:沿用Container_Valid检查构造及借用对象,不截断地址,不决定是否允许广播。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Set_CA(IEC60870_Asdu_t *self, uint16_t common_address)
|
||
{
|
||
if (!Container_Valid(self) || (self->parameters->sizeOfCA == 1 && common_address > 255U)) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
IEC60870_Object_Write_LE(self->buffer + 2 + self->parameters->sizeOfCOT,
|
||
common_address, (unsigned int)self->parameters->sizeOfCA);
|
||
return IEC60870_ASDU_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:编码并追加一个同类型对象,成功后更新ASDU长度及VSQ。
|
||
* 输入参数:self——已成功Create且控制字段/参数/原文未被修改的容器。
|
||
* object——已由具体类型Create或解析器初始化的公共基类首成员。
|
||
* 输出参数:self——成功后更新容器及其字节缓冲区,失败保持原值。
|
||
* 返回值:OK为追加成功;FULL为字节空间或对象数已满;UNSUPPORTED为未知类型;
|
||
* ERROR为容器、身份、字段、地址或SQ约束非法。
|
||
* 说明:在有界临时区完成IOA和值编码(含变长文件段),确认容量后才写入。
|
||
* 首个成功对象固定Type ID;SQ=1首对象仍带IOA,其后只写载荷且地址必须递增1。
|
||
* FULL时由调用方提交已有非空ASDU,另起一条重试同一对象;不会自动拆包。
|
||
* 成功后仅保存对象的报文字节,不借用object,调用方可以立即复用其存储。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Add_InformationObject(IEC60870_Asdu_t *self,
|
||
const IEC60870_InformationObject_t *object)
|
||
{
|
||
const IEC60870_InformationObjectVTable *type;
|
||
uint8_t encoded[IEC60870_OBJECT_MAX_ENCODED];
|
||
uint16_t added;
|
||
IEC60870_AsduResult_t status;
|
||
if (!Container_Valid(self) || self->is_from_buffer || object == NULL) { return IEC60870_ASDU_ERROR; }
|
||
type = IEC60870_InformationObject_Get_Descriptor(object->type_id);
|
||
if (type == NULL) { return IEC60870_ASDU_UNSUPPORTED; }
|
||
if (object->virtual_function_table != type ||
|
||
(self->object_count != 0U && self->type_id != type->type_id) ||
|
||
(self->is_sequence && !type->supports_sequence) ||
|
||
object->ioa > IEC60870_Object_Max_IOA(self->parameters)) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
/* 用减法先判断地址范围,再比较首IOA+已有数量,避免允许隐含地址越界。 */
|
||
if (self->is_sequence && self->object_count != 0U &&
|
||
(self->first_ioa > IEC60870_Object_Max_IOA(self->parameters) - self->object_count ||
|
||
object->ioa != self->first_ioa + self->object_count)) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
/* 字段错误优先于容量已满,以免把非法对象误当作下一条ASDU的可重试输入。 */
|
||
status = IEC60870_InformationObject_Encode(object, self->parameters,
|
||
self->is_sequence && self->object_count != 0U, encoded, sizeof(encoded), &added);
|
||
if (status != IEC60870_ASDU_OK) { return status; }
|
||
if (self->object_count == IEC60870_ASDU_MAX_OBJECTS ||
|
||
(type->single_object && self->object_count != 0U) ||
|
||
added > self->capacity - self->length) {
|
||
return IEC60870_ASDU_FULL;
|
||
}
|
||
/* 从此处开始统一提交,没有后续失败分支,容量不足不会留下半个对象。 */
|
||
memcpy(self->buffer + self->length, encoded, added);
|
||
if (self->object_count == 0U) {
|
||
self->first_ioa = object->ioa;
|
||
self->type_id = type->type_id;
|
||
self->buffer[0] = type->type_id;
|
||
}
|
||
self->length = (uint16_t)(self->length + added);
|
||
++self->object_count;
|
||
self->buffer[1] = (uint8_t)(self->object_count | (self->is_sequence << 7));
|
||
return IEC60870_ASDU_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:导出构造完成或直接借用的ASDU原文。
|
||
* 输入参数:self——非空合法对象;Create创建的容器必须至少加入一个对象。
|
||
* 输出参数:asdu——非空输出指针,成功时指向调用方原有缓冲区。
|
||
* length——非空输出指针,成功时得到当前ASDU字节数,不含APCI。
|
||
* 返回值:OK为成功;ERROR为容器、输出参数非法或容器为空;失败不修改输出。
|
||
* 说明:返回借用视图,不复制或发送。借用原文对象不额外进行完整对象校验。
|
||
* 后续Add/Create/Set会影响原文,length只是本次快照。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Get_Data(const IEC60870_Asdu_t *self,
|
||
const uint8_t **asdu, uint16_t *length)
|
||
{
|
||
if (!Container_Valid(self) || (!self->is_from_buffer && self->object_count == 0U) ||
|
||
asdu == NULL || length == NULL) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
*asdu = self->buffer;
|
||
*length = self->length;
|
||
return IEC60870_ASDU_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:校验完整ASDU(含参数和文件类型),生成可用于逐对象提取的只读视图。
|
||
* 输入参数:parameters——显式非空应用层参数,字段宽度必须对应收到的报文。
|
||
* asdu——非空ASDU原文,不含APCI。
|
||
* length——完整ASDU字节数,不能超过配置上限,不能包含多余尾字节。
|
||
* 输出参数:info——非空视图输出,借用parameters和asdu,二者须在取对象期间有效且不变。
|
||
* 返回值:OK为成功;UNSUPPORTED为未支持类型;ERROR为公共头、布局或任一对象非法。
|
||
* 说明:先检查公共头和布局,再验证所有对象,全部成功后才发布info,失败保留旧输出。
|
||
* 不执行命令,也不判断COT/CA/IOA的业务适用性;具体对象通过Get_Element获取。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Parse(const IEC60870_AppLayerParameters_t *parameters,
|
||
const uint8_t *asdu, uint16_t length, IEC60870_AsduInfo_t *info)
|
||
{
|
||
IEC60870_AsduInfo_t result;
|
||
const IEC60870_InformationObjectVTable *type;
|
||
uint8_t i;
|
||
if (info == NULL || !IEC60870_Parse_ASDU_Header(parameters, asdu, length, &result)) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
type = IEC60870_InformationObject_Get_Descriptor(result.type_id);
|
||
if (type == NULL) { return IEC60870_ASDU_UNSUPPORTED; }
|
||
if (!Layout_Valid(&result, type)) { return IEC60870_ASDU_ERROR; }
|
||
/* 输出传NULL使类型解析器仅校验,不分配对象数组;任一对象失败就丢弃整个临时视图。 */
|
||
for (i = 0; i < result.object_count; ++i) {
|
||
uint32_t ioa;
|
||
uint16_t payload_length;
|
||
const uint8_t *payload = Element_Position(&result, type, i, &ioa, &payload_length);
|
||
IEC60870_AsduResult_t status = IEC60870_InformationObject_Decode(result.type_id, ioa,
|
||
payload, payload_length, NULL, 0);
|
||
if (status != IEC60870_ASDU_OK) { return status; }
|
||
}
|
||
*info = result;
|
||
return IEC60870_ASDU_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:从已解析视图中按索引取得一个独立类型的对象副本。
|
||
* 输入参数:info——由Asdu_Parse生成且未修改的视图,其原文和参数仍须有效。
|
||
* index——从0开始的对象索引,必须小于object_count。
|
||
* object_size——object实际可用字节数,至少为对应具体结构的sizeof。
|
||
* 输出参数:object——非空具体对象存储,结构类型必须由info->type_id正确选择。
|
||
* 返回值:OK为成功;UNSUPPORTED为未知类型;ERROR为布局、索引、载荷或输出尺寸非法。
|
||
* 说明:输出无需预先Create,成功时按值保存且不借用原文,失败保持原输出不变。
|
||
* 只重新检查布局和选中对象;尺寸校验不能替代C语言类型匹配,公共头仍从info读取。
|
||
*/
|
||
IEC60870_AsduResult_t IEC60870_Asdu_Get_Element(const IEC60870_AsduInfo_t *info,
|
||
uint8_t index, void *object, size_t object_size)
|
||
{
|
||
const IEC60870_InformationObjectVTable *type;
|
||
const uint8_t *payload;
|
||
uint32_t ioa;
|
||
uint16_t payload_length;
|
||
if (info == NULL || object == NULL) { return IEC60870_ASDU_ERROR; }
|
||
type = IEC60870_InformationObject_Get_Descriptor(info->type_id);
|
||
if (type == NULL) { return IEC60870_ASDU_UNSUPPORTED; }
|
||
if (!Layout_Valid(info, type) || index >= info->object_count) { return IEC60870_ASDU_ERROR; }
|
||
payload = Element_Position(info, type, index, &ioa, &payload_length);
|
||
return IEC60870_InformationObject_Decode(info->type_id, ioa, payload,
|
||
payload_length, object, object_size);
|
||
}
|
||
|
||
/**
|
||
* 功能:复制请求原文并改写COT/P/N,构造确认、终止或未知字段错误应答。
|
||
* 输入参数:parameters——与请求布局匹配的非空合法应用层参数。
|
||
* request——非空请求ASDU原文,不含APCI。
|
||
* length——请求ASDU的完整字节数。
|
||
* cause——接受7、9、10及44..47;分别用于确认、终止及未知类型/原因/地址。
|
||
* is_negative——P/N标志,0或1;否定激活确认使用cause=7、is_negative=1。
|
||
* capacity——out实际可写字节数,至少为length。
|
||
* 输出参数:out——非空应答缓冲区,可与request原地或部分重叠。
|
||
* out_length——非空长度输出,成功时等于length。
|
||
* 返回值:OK为成功,FULL为输出容量不足,ERROR为参数或请求基础格式非法。
|
||
* 说明:只依赖公共头和最低长度检查,不要求具体对象解析成功,因此可回显未知类型。
|
||
* 保留TEST、OA、CA、VSQ及所有对象字节;失败不修改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)
|
||
{
|
||
IEC60870_AsduInfo_t info;
|
||
uint8_t cot;
|
||
if (out == NULL || out_length == NULL || (is_negative != 0 && is_negative != 1) ||
|
||
(cause != IEC60870_ASDU_COT_ACTIVATION_CON && cause != IEC60870_ASDU_COT_DEACTIVATION_CON &&
|
||
cause != IEC60870_ASDU_COT_ACTIVATION_TERMINATION &&
|
||
(cause < IEC60870_ASDU_COT_UNKNOWN_TYPE || cause > IEC60870_ASDU_COT_UNKNOWN_IOA)) ||
|
||
!IEC60870_Parse_ASDU_Header(parameters, request, length, &info)) {
|
||
return IEC60870_ASDU_ERROR;
|
||
}
|
||
if (capacity < length) { return IEC60870_ASDU_FULL; }
|
||
/* 先保存新COT,再memmove原文;即使输入输出重叠,也不会丢失请求的TEST。 */
|
||
cot = (uint8_t)(cause | (is_negative << 6) | (info.is_test << 7));
|
||
memmove(out, request, length);
|
||
out[2] = cot;
|
||
*out_length = length;
|
||
return IEC60870_ASDU_OK;
|
||
}
|