300 lines
15 KiB
C
300 lines
15 KiB
C
/**
|
||
* 每连接独立的IEC-104流式接收器。
|
||
* 每个实例只保留一帧;完整帧经APCI及ASDU最低长度检查后暂停输入,显式释放后再处理剩余字节。
|
||
* Codec负责报文格式,Link/SendManager负责序号、ACK和启停;本模块不改变链路状态。
|
||
*/
|
||
#include "IEC104_Receiver.h"
|
||
#include "IEC60870_Codec.h"
|
||
|
||
#include <stddef.h>
|
||
#include <string.h>
|
||
|
||
/**
|
||
* 功能:丢弃当前候选帧,将接收器恢复到等待帧头的阶段。
|
||
* 输入参数:receiver——非NULL接收器,调用方已核对初始化状态和连接代次。
|
||
* 输出参数:receiver——接收长度、期望长度及完整帧标志清零。
|
||
* 返回值:无。
|
||
* 说明:保留连接代次、初始化标志和缓冲字节;旧字节不再属于有效报文。
|
||
* 如此前已交付完整帧视图,该视图随本次清除失效。
|
||
*/
|
||
static void Clear_Progress(IEC104_Receiver_t *receiver)
|
||
{
|
||
receiver->received_length = 0U;
|
||
receiver->expected_length = 0U;
|
||
receiver->is_frame_ready = 0U;
|
||
}
|
||
|
||
/**
|
||
* 功能:检查接收器是否已初始化,以及本次操作是否属于当前连接代次。
|
||
* 输入参数:receiver——待检查对象,允许NULL;connection_generation——本次操作所属代次。
|
||
* 输出参数:无。
|
||
* 返回值:对象已初始化且非零代次相同返回1,否则0。
|
||
* 说明:不检查组帧进度,因此Reset可以在进度字段异常时仍清除当前报文。
|
||
* 代次由连接层分配;本模块只比较代次,不负责生成或递增。
|
||
*/
|
||
static int Session_Valid(const IEC104_Receiver_t *receiver, uint64_t connection_generation)
|
||
{
|
||
return receiver != NULL && receiver->is_initialized == 1U &&
|
||
connection_generation != 0U && receiver->connection_generation == connection_generation;
|
||
}
|
||
|
||
/**
|
||
* 功能:检查连接身份、接收阶段、帧头和长度字段是否自洽。
|
||
* 输入参数:receiver——待检查对象,允许NULL;connection_generation——本次操作所属代次。
|
||
* 输出参数:无。
|
||
* 返回值:状态及已知边界合法返回1,否则0。
|
||
* 说明:未收到长度域时expected_length必须为0;收到后必须与长度域一致。
|
||
* 只有收齐报文时才能置完整帧标志。本函数不解析APCI,也不检查链路序号。
|
||
*/
|
||
static int Receiver_Valid(const IEC104_Receiver_t *receiver, uint64_t connection_generation)
|
||
{
|
||
if (!Session_Valid(receiver, connection_generation) || receiver->is_frame_ready > 1U) {
|
||
return 0;
|
||
}
|
||
/* 尚未收到帧头:没有候选帧,也不能存在期望长度或完整帧标志。 */
|
||
if (receiver->received_length == 0U) {
|
||
return receiver->expected_length == 0U && receiver->is_frame_ready == 0U;
|
||
}
|
||
if (receiver->buffer[0] != IEC104_START_BYTE) {
|
||
return 0;
|
||
}
|
||
/* 仅收到0x68:长度域尚未知,不能用缓冲中遗留的第二字节判断报文长度。 */
|
||
if (receiver->received_length == 1U) {
|
||
return receiver->expected_length == 0U && receiver->is_frame_ready == 0U;
|
||
}
|
||
/* 长度域不包含起始字节和长度字节,因此APDU总长为长度域加2。 */
|
||
if (receiver->buffer[1] < IEC104_MIN_LENGTH_FIELD || receiver->buffer[1] > IEC104_MAX_LENGTH_FIELD ||
|
||
receiver->expected_length != (uint16_t)(2U + receiver->buffer[1]) ||
|
||
receiver->expected_length > sizeof(receiver->buffer) ||
|
||
receiver->received_length > receiver->expected_length) {
|
||
return 0;
|
||
}
|
||
/* 完整帧等待释放;未就绪的候选帧则必须仍缺少至少一个字节。 */
|
||
return receiver->is_frame_ready != 0U ?
|
||
receiver->received_length == receiver->expected_length :
|
||
receiver->received_length < receiver->expected_length;
|
||
}
|
||
|
||
/* 成帧及借用共用格式检查;I帧在交给Link/Manager前检查已知对象的最低载荷长度。
|
||
* APCI解析也供发送缓冲使用,ASDU接收检查在此单独组合,不改变发送构帧路径。 */
|
||
static int Parse_Received_Frame(const IEC104_Receiver_t *receiver, IEC104_ApciInfo_t *apci)
|
||
{
|
||
/* 本接收器属于104传输,显式使用104标准应用层布局。 */
|
||
static const IEC60870_AppLayerParameters_t parameters = IEC104_APP_LAYER_PARAMETERS_DEFAULT;
|
||
IEC60870_AsduInfo_t asdu_info;
|
||
|
||
if (!IEC104_Parse_APCI(receiver->buffer, receiver->received_length, apci)) {
|
||
return 0;
|
||
}
|
||
return apci->frame_type != IEC104_FRAME_TYPE_I ||
|
||
IEC60870_Parse_ASDU_Header(¶meters, apci->asdu, apci->asdu_len, &asdu_info);
|
||
}
|
||
|
||
/**
|
||
* 功能:初始化独立接收器,并绑定一个非零连接代次。
|
||
* 输入参数:receiver——调用方提供的存储;connection_generation——连接层分配的代次。
|
||
* 输出参数:receiver——成功时清零全部字段和缓冲,再记录代次及初始化标志。
|
||
* 返回值:OK表示成功;ERROR表示指针为空或代次为0,对象保持不变。
|
||
* 说明:首次使用及新连接换代都调用本函数;重新初始化会使已有帧视图失效。
|
||
* 调用前须结束旧连接及其I/O,不得与Feed或视图读取并发执行。
|
||
*/
|
||
IEC104_ReceiverResult_t IEC104_Receiver_Init(
|
||
IEC104_Receiver_t *receiver, uint64_t connection_generation)
|
||
{
|
||
if (receiver == NULL || connection_generation == 0U) {
|
||
return IEC104_RECEIVER_ERROR;
|
||
}
|
||
memset(receiver, 0, sizeof(*receiver));
|
||
receiver->connection_generation = connection_generation;
|
||
receiver->is_initialized = 1U;
|
||
return IEC104_RECEIVER_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:读取接收进度,避免上层直接依赖接收器内部的成帧标志和长度字段。
|
||
* 输入参数:receiver/connection_generation——当前对象和代次;status——不重叠的输出存储。
|
||
* 返回值:OK写数值快照;ERROR参数、身份或进度非法且不改输出。查询不消费字节或释放原文。
|
||
*/
|
||
IEC104_ReceiverResult_t IEC104_Receiver_Get_Status(
|
||
const IEC104_Receiver_t *receiver, uint64_t connection_generation, IEC104_ReceiverStatus_t *status)
|
||
{
|
||
IEC104_ReceiverStatus_t prepared;
|
||
if (status == NULL || !Receiver_Valid(receiver, connection_generation)) {
|
||
return IEC104_RECEIVER_ERROR;
|
||
}
|
||
prepared.received_length = receiver->received_length;
|
||
prepared.expected_length = receiver->expected_length;
|
||
prepared.has_complete_frame = receiver->is_frame_ready;
|
||
*status = prepared;
|
||
return IEC104_RECEIVER_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:在连接收尾时清除接收缓冲及绑定,所有旧视图随即失效。
|
||
* 输入参数:receiver——待清理对象,允许NULL;调用方必须已经结束旧I/O及字节借用。
|
||
* 返回值:无。不调用Link或socket;之后重新使用须Init,不能直接Feed或Reset。
|
||
*/
|
||
void IEC104_Receiver_Abort(IEC104_Receiver_t *receiver)
|
||
{
|
||
if (receiver != NULL) {
|
||
memset(receiver, 0, sizeof(*receiver));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 功能:将按序到达的字节片段拼成一帧完整APDU,检查APCI及ASDU最低长度。
|
||
* 输入参数:receiver——已初始化接收器;connection_generation——本次输入所属代次;
|
||
* data——输入片段首地址,length为0时允许NULL;length——输入字节数。
|
||
* 输出参数:consumed——非NULL,写入本次实际消费数,格式错误时包括触发错误的字节;
|
||
* receiver——保存半帧,或保留一帧已通过格式及最低长度检查的完整报文。
|
||
* 返回值:FRAME_READY表示已有完整帧;IN_PROGRESS表示输入用完但尚无完整帧;
|
||
* FRAME_ERROR表示候选帧格式非法且已丢弃;ERROR表示参数、代次或内部状态非法。
|
||
* 说明:每次最多完成一帧;未消费的尾部由调用方保留,当前帧释放后再按原顺序递入。
|
||
* ERROR时接收器不变,有效consumed输出为0;输入、输出及接收器存储不得重叠。
|
||
* 这里只检查报文格式,不判断ASDU业务合法性、链路序号及启停状态。
|
||
*/
|
||
IEC104_ReceiverResult_t IEC104_Receiver_Feed(
|
||
IEC104_Receiver_t *receiver, uint64_t connection_generation,
|
||
const uint8_t *data, uint32_t length, uint32_t *consumed)
|
||
{
|
||
IEC104_ApciInfo_t apci;
|
||
|
||
if (consumed == NULL) {
|
||
return IEC104_RECEIVER_ERROR;
|
||
}
|
||
*consumed = 0U;
|
||
if (!Receiver_Valid(receiver, connection_generation) || (length != 0U && data == NULL)) {
|
||
return IEC104_RECEIVER_ERROR;
|
||
}
|
||
/* 上一帧尚未释放时暂停消费,保证调用方正在读取的帧内容不会被覆盖。 */
|
||
if (receiver->is_frame_ready != 0U) {
|
||
return IEC104_RECEIVER_FRAME_READY;
|
||
}
|
||
|
||
while (*consumed < length) {
|
||
uint32_t copy_length;
|
||
|
||
/* 阶段一:帧边界的首字节必须为0x68;不跳过垃圾重新同步。 */
|
||
if (receiver->received_length == 0U) {
|
||
uint8_t byte = data[(*consumed)++];
|
||
if (byte != IEC104_START_BYTE) {
|
||
Clear_Progress(receiver);
|
||
return IEC104_RECEIVER_FRAME_ERROR;
|
||
}
|
||
receiver->buffer[0] = byte;
|
||
receiver->received_length = 1U;
|
||
continue;
|
||
}
|
||
/* 阶段二:读取长度域。允许4..253,对应包含起始字节和长度字节的总长6..255。 */
|
||
if (receiver->received_length == 1U) {
|
||
uint8_t byte = data[(*consumed)++];
|
||
if (byte < IEC104_MIN_LENGTH_FIELD || byte > IEC104_MAX_LENGTH_FIELD) {
|
||
/* 非法长度字节已消费;立即返回,让调用方决定如何处理尚未消费的尾部。 */
|
||
Clear_Progress(receiver);
|
||
return IEC104_RECEIVER_FRAME_ERROR;
|
||
}
|
||
receiver->buffer[1] = byte;
|
||
receiver->received_length = 2U;
|
||
receiver->expected_length = (uint16_t)(2U + byte);
|
||
continue;
|
||
}
|
||
|
||
/* 阶段三:按长度收齐控制域及可选ASDU;报文体中的0x68不再当作帧头。
|
||
* 复制量取“当前帧缺少的字节数”和“本次剩余输入”中的较小值,避免吞掉下一帧。 */
|
||
copy_length = (uint32_t)(receiver->expected_length - receiver->received_length);
|
||
if (copy_length > length - *consumed) {
|
||
copy_length = length - *consumed;
|
||
}
|
||
memcpy(receiver->buffer + receiver->received_length, data + *consumed, copy_length);
|
||
receiver->received_length = (uint16_t)(receiver->received_length + copy_length);
|
||
*consumed += copy_length;
|
||
|
||
if (receiver->received_length == receiver->expected_length) {
|
||
/* 长度收齐后仍须检查控制域、ASDU公共头和已知对象的最低载荷长度。 */
|
||
if (!Parse_Received_Frame(receiver, &apci)) {
|
||
/* 整个非法候选帧已消费,只清除组帧进度,不继续搜索其内部字节。 */
|
||
Clear_Progress(receiver);
|
||
return IEC104_RECEIVER_FRAME_ERROR;
|
||
}
|
||
/* 完整帧留在内部缓冲,直到调用方Peek、处理并显式Release。 */
|
||
receiver->is_frame_ready = 1U;
|
||
return IEC104_RECEIVER_FRAME_READY;
|
||
}
|
||
}
|
||
/* 包括length为0且无完整帧的情况;保留当前进度,等待后续片段。 */
|
||
return IEC104_RECEIVER_IN_PROGRESS;
|
||
}
|
||
|
||
/**
|
||
* 功能:取得当前完整帧的只读借用视图,不消费或释放报文。
|
||
* 输入参数:receiver——已初始化接收器;connection_generation——本次读取所属代次。
|
||
* 输出参数:frame——非NULL且不与receiver重叠,仅成功时写入原文地址、长度和APCI结果。
|
||
* 返回值:FRAME_READY表示视图有效;EMPTY表示尚无完整帧;ERROR表示参数或状态非法。
|
||
* 说明:允许重复读取,但同一帧被业务受理后不能重复分派;本函数不修改接收器。
|
||
* 视图引用内部缓冲,成功Release、Reset或重新Init后失效;需要长期保存时须复制。
|
||
*/
|
||
IEC104_ReceiverResult_t IEC104_Receiver_Peek_Frame(
|
||
const IEC104_Receiver_t *receiver, uint64_t connection_generation,
|
||
IEC104_ReceivedFrame_t *frame)
|
||
{
|
||
IEC104_ReceivedFrame_t prepared;
|
||
|
||
if (frame == NULL || !Receiver_Valid(receiver, connection_generation)) {
|
||
return IEC104_RECEIVER_ERROR;
|
||
}
|
||
if (receiver->is_frame_ready == 0U) {
|
||
return IEC104_RECEIVER_EMPTY;
|
||
}
|
||
memset(&prepared, 0, sizeof(prepared));
|
||
/* 重新解析到调用方视图,避免在Receiver对象中缓存指向自身缓冲的指针。 */
|
||
if (!Parse_Received_Frame(receiver, &prepared.apci)) {
|
||
return IEC104_RECEIVER_ERROR;
|
||
}
|
||
prepared.connection_generation = receiver->connection_generation;
|
||
prepared.apdu = receiver->buffer;
|
||
prepared.apdu_length = receiver->received_length;
|
||
/* 临时结果全部准备成功后再写输出,避免失败时向调用方交付不完整的视图。 */
|
||
*frame = prepared;
|
||
return IEC104_RECEIVER_FRAME_READY;
|
||
}
|
||
|
||
/**
|
||
* 功能:释放已处理的完整帧,允许接收器继续消费后续输入。
|
||
* 输入参数:receiver——已初始化接收器;connection_generation——本次释放所属代次。
|
||
* 输出参数:receiver——成功时清除组帧进度;缓冲字节、连接代次及初始化标志保留。
|
||
* 返回值:OK表示已释放;EMPTY表示没有完整帧,已有半帧继续保留;ERROR表示状态非法。
|
||
* 说明:释放后旧视图立即失效;此处只复用接收空间,不发送ACK、不推进链路状态。
|
||
* 业务暂未受理当前帧时不能调用本函数,应保留当前帧等待后续处理。
|
||
*/
|
||
IEC104_ReceiverResult_t IEC104_Receiver_Release_Frame(
|
||
IEC104_Receiver_t *receiver, uint64_t connection_generation)
|
||
{
|
||
if (!Receiver_Valid(receiver, connection_generation)) {
|
||
return IEC104_RECEIVER_ERROR;
|
||
}
|
||
if (receiver->is_frame_ready == 0U) {
|
||
return IEC104_RECEIVER_EMPTY;
|
||
}
|
||
Clear_Progress(receiver);
|
||
return IEC104_RECEIVER_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:显式丢弃当前半帧或完整帧,重新等待帧头。
|
||
* 输入参数:receiver——已初始化接收器;connection_generation——本次复位所属代次。
|
||
* 输出参数:receiver——成功时清除组帧进度,保留代次、初始化标志和缓冲字节。
|
||
* 返回值:OK表示复位完成;ERROR表示对象未初始化或代次非法,对象保持不变。
|
||
* 说明:与Release不同,无需已经收齐一帧,也允许清除异常进度;已有视图随之失效。
|
||
* 只复位接收器,不复位Link;新连接换代须调用Init,不能用Reset替代。
|
||
*/
|
||
IEC104_ReceiverResult_t IEC104_Receiver_Reset(
|
||
IEC104_Receiver_t *receiver, uint64_t connection_generation)
|
||
{
|
||
/* 此处仅核对连接身份,避免进度字段异常时反而无法执行清除。 */
|
||
if (!Session_Valid(receiver, connection_generation)) {
|
||
return IEC104_RECEIVER_ERROR;
|
||
}
|
||
Clear_Progress(receiver);
|
||
return IEC104_RECEIVER_OK;
|
||
}
|