Files
STM32F4-Base/IEC60870-Slave/common/IEC60870_Asdu.c
T
STF 4c55544f05
CI / L0 静态门禁 (pre-commit) (push) Failing after 45s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
通信协议代码
2026-10-26 08:31:48 -04:00

462 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}