/* * ASDU层仅处理公共头、对象布局、容量和分派。 * 对象编码调用基类函数表;对象解码按Type ID调用独立类型解析器。 * 所有外部可见写入都在字段、布局和容量验证完成后提交,失败不留下半条报文。 */ #include "IEC60870_Asdu.h" #include "IEC60870_InformationObjects_Internal.h" #include /** * 功能:计算当前配置的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; }