928 lines
43 KiB
Plaintext
928 lines
43 KiB
Plaintext
/*
|
|
* IEC104单点/双点遥控(45/46)。
|
|
* 一个事务槽保存工作事件及必要回复,独立短应答使用固定池;全局序号决定提交先后。
|
|
* 应用显式Accept启动动作,Report_Result结束设备期限,Complete结束返回信息阶段。
|
|
* FINISHED和发送收尾分别完成后才释放事务;取消不回滚设备,迟到事件不能复用新事务。
|
|
*/
|
|
#include "IEC104_Command.h"
|
|
#include "IEC104_TcpServer_Config.h"
|
|
#include "IEC104_Net.h"
|
|
#include <string.h>
|
|
|
|
#ifndef IEC104_COMMAND_CAPACITY
|
|
#define IEC104_COMMAND_CAPACITY IEC104_TCP_SERVER_CAPACITY
|
|
#endif
|
|
#ifndef IEC104_COMMAND_REPLY_CAPACITY
|
|
#define IEC104_COMMAND_REPLY_CAPACITY 4U
|
|
#endif
|
|
_Static_assert(IEC104_COMMAND_CAPACITY > 0U, "Command needs instance storage");
|
|
_Static_assert(IEC104_COMMAND_REPLY_CAPACITY > 0U &&
|
|
IEC104_COMMAND_REPLY_CAPACITY < UINT32_MAX / 2U, "Command needs bounded reply storage");
|
|
|
|
typedef struct {
|
|
IEC104_SlaveEventIdentity_t identity;
|
|
uint8_t request_bytes[10], reply_bytes[10];
|
|
IEC104_Station_t *station;
|
|
uint64_t transaction_id, receive_serial, accepted_ms, timer_start_ms;
|
|
uint64_t event_serial, accepted_event_serial, select_event_serial, execute_event_serial;
|
|
uint64_t confirm_serial, term_serial, reply_serial;
|
|
uint8_t phase, event_type, finish_reason, result, confirm_cot, reply_cot;
|
|
bool occupied, event_taken, ever_work_delivered, finish_pending;
|
|
bool select_delivered, execute_delivered, selected;
|
|
bool confirm_negative, confirm_try, send_waiting, return_waiting;
|
|
bool confirm_submitted, completed, term_submitted, select_effect, execute_effect;
|
|
} CommandTransaction;
|
|
|
|
typedef struct {
|
|
IEC104_SlaveEventIdentity_t identity;
|
|
uint8_t bytes[10];
|
|
uint64_t serial;
|
|
uint8_t cot;
|
|
bool occupied, send_waiting;
|
|
} CommandReply;
|
|
|
|
struct IEC104_Command {
|
|
IEC104_Slave_t *slave;
|
|
IEC104_SlaveEventIdentity_t session;
|
|
IEC104_CommandConfig_t config;
|
|
IEC104_CommandStatus_t status;
|
|
CommandTransaction transaction;
|
|
CommandReply replies[IEC104_COMMAND_REPLY_CAPACITY];
|
|
uint64_t serial, next_transaction, next_event, next_reply, last_generation;
|
|
bool in_use, in_action;
|
|
};
|
|
|
|
static IEC104_Command_t command_pool[IEC104_COMMAND_CAPACITY];
|
|
static uint64_t next_instance;
|
|
|
|
/* 先验证静态池归属,再解引用。 */
|
|
static bool Owned(const IEC104_Command_t *self)
|
|
{
|
|
unsigned i;
|
|
for (i = 0U; i < IEC104_COMMAND_CAPACITY; ++i) {
|
|
if (self == &command_pool[i]) { return self->in_use; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint32_t Read_IOA(const uint8_t *bytes)
|
|
{
|
|
return (uint32_t)bytes[6] | ((uint32_t)bytes[7] << 8) | ((uint32_t)bytes[8] << 16);
|
|
}
|
|
|
|
static uint16_t Read_CA(const uint8_t *bytes)
|
|
{
|
|
return (uint16_t)((uint16_t)bytes[4] | ((uint16_t)bytes[5] << 8));
|
|
}
|
|
|
|
/* 所有事件及动作使用模块保留的请求重建,不能信任回传event中的可修改request。 */
|
|
static void Copy_Request(const CommandTransaction *task, IEC104_CommandRequest_t *request)
|
|
{
|
|
memset(request, 0, sizeof(*request));
|
|
request->generation = task->identity.generation;
|
|
request->receive_serial = task->receive_serial;
|
|
request->station_id = IEC104_Station_Get_Id(task->station);
|
|
request->common_address = Read_CA(task->request_bytes);
|
|
request->originator_address = task->request_bytes[3];
|
|
request->type_id = task->request_bytes[0];
|
|
request->ioa = Read_IOA(task->request_bytes);
|
|
request->is_select = (task->request_bytes[9] & 0x80U) != 0U;
|
|
request->selected = task->selected;
|
|
request->state = task->request_bytes[9] & (request->type_id == 45U ? 1U : 3U);
|
|
request->qualifier = (task->request_bytes[9] >> 2) & 31U;
|
|
request->accepted_ms = task->accepted_ms;
|
|
}
|
|
|
|
static unsigned Pending_Replies(const IEC104_Command_t *self)
|
|
{
|
|
const CommandTransaction *task = &self->transaction;
|
|
unsigned i, count = (task->confirm_serial != 0U ? 1U : 0U) +
|
|
(task->term_serial != 0U ? 1U : 0U) + (task->reply_serial != 0U ? 1U : 0U);
|
|
for (i = 0U; i < IEC104_COMMAND_REPLY_CAPACITY; ++i) {
|
|
if (self->replies[i].occupied) { ++count; }
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* FINISHED消费与本事务回复交接是两个独立条件;独立短应答不借用事务内存。 */
|
|
static void Reap(IEC104_Command_t *self)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
if (!task->occupied || task->phase != IEC104_COMMAND_PHASE_ENDED || task->finish_pending ||
|
|
task->confirm_serial != 0U || task->term_serial != 0U || task->reply_serial != 0U) { return; }
|
|
(void)IEC104_Slave_Release_Station(self->slave, task->station);
|
|
memset(task, 0, sizeof(*task));
|
|
}
|
|
|
|
/* 第一次终结冻结原因并移除未读工作事件。后续STOP/Cancel只可清回复,不能重发FINISHED。 */
|
|
static void Finish(IEC104_Command_t *self, IEC104_CommandFinishReason_t reason)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
if (!task->occupied || task->phase == IEC104_COMMAND_PHASE_ENDED) { Reap(self); return; }
|
|
task->phase = IEC104_COMMAND_PHASE_ENDED;
|
|
task->finish_reason = (uint8_t)reason;
|
|
task->event_type = IEC104_COMMAND_FINISHED;
|
|
task->event_taken = false;
|
|
task->finish_pending = task->ever_work_delivered;
|
|
task->return_waiting = false;
|
|
/* 创建事务时为最多两个工作事件和一个FINISHED检查了序号余量。 */
|
|
if (task->finish_pending) { task->event_serial = ++self->next_event; }
|
|
switch (reason) {
|
|
case IEC104_COMMAND_FINISH_COMPLETED: ++self->status.completed; break;
|
|
case IEC104_COMMAND_FINISH_EXECUTION_FAILED: ++self->status.execution_failed; break;
|
|
case IEC104_COMMAND_FINISH_ACTION_FAILED: ++self->status.action_failed; break;
|
|
case IEC104_COMMAND_FINISH_MISMATCH: ++self->status.mismatches; break;
|
|
case IEC104_COMMAND_FINISH_DEACTIVATED: ++self->status.deactivated; break;
|
|
case IEC104_COMMAND_FINISH_SELECT_TIMEOUT: ++self->status.select_timeouts; break;
|
|
case IEC104_COMMAND_FINISH_DECISION_TIMEOUT: ++self->status.decision_timeouts; break;
|
|
case IEC104_COMMAND_FINISH_EXECUTE_TIMEOUT: ++self->status.execute_timeouts; break;
|
|
case IEC104_COMMAND_FINISH_CANCELLED:
|
|
case IEC104_COMMAND_FINISH_STOPPED:
|
|
case IEC104_COMMAND_FINISH_DISCONNECTED:
|
|
case IEC104_COMMAND_FINISH_CONNECTION_CHANGED:
|
|
case IEC104_COMMAND_FINISH_SEND_ERROR:
|
|
case IEC104_COMMAND_FINISH_SERVICE_ERROR: ++self->status.cancelled; break;
|
|
default: break;
|
|
}
|
|
Reap(self);
|
|
}
|
|
|
|
static void Cancel_All(IEC104_Command_t *self, IEC104_CommandFinishReason_t reason)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
task->confirm_serial = task->term_serial = task->reply_serial = 0U;
|
|
task->send_waiting = false;
|
|
memset(self->replies, 0, sizeof(self->replies));
|
|
Finish(self, reason);
|
|
}
|
|
|
|
/* 动作/提交门禁,不用于消费Handle_Event本身,否则FIFO中尚有通知时无法读空。 */
|
|
static IEC104_SlaveRequestState_t Check_Session(IEC104_Command_t *self)
|
|
{
|
|
IEC104_SlaveRequestState_t state = IEC104_Slave_Check_Request(self->slave, &self->session);
|
|
self->status.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
|
|
if (state == IEC104_SLAVE_REQUEST_ERROR) {
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
|
|
self->status.active = false;
|
|
} else if (state != IEC104_SLAVE_REQUEST_WAIT_EVENTS &&
|
|
(!self->status.active || state != IEC104_SLAVE_REQUEST_READY)) {
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_CONNECTION_CHANGED);
|
|
self->status.active = false;
|
|
state = IEC104_SLAVE_REQUEST_STALE;
|
|
}
|
|
return state;
|
|
}
|
|
|
|
/* 期限关闭返回0;动作回调中事件已消费,不再计算决定期限。 */
|
|
static uint32_t Timer_Limit(const IEC104_Command_t *self)
|
|
{
|
|
const CommandTransaction *task = &self->transaction;
|
|
if (!task->occupied || self->in_action) { return 0U; }
|
|
switch (task->phase) {
|
|
case IEC104_COMMAND_PHASE_SELECT_WAIT:
|
|
case IEC104_COMMAND_PHASE_EXECUTE_WAIT: return self->config.decision_timeout_ms;
|
|
case IEC104_COMMAND_PHASE_SELECTED: return self->config.select_timeout_ms;
|
|
case IEC104_COMMAND_PHASE_EXECUTING:
|
|
return task->result == 0U ? self->config.execute_timeout_ms : 0U;
|
|
default: return 0U;
|
|
}
|
|
}
|
|
|
|
static uint32_t Remaining(const CommandTransaction *task, uint32_t limit, uint64_t now)
|
|
{
|
|
if (now < task->timer_start_ms || now - task->timer_start_ms >= limit) { return 0U; }
|
|
return limit - (uint32_t)(now - task->timer_start_ms);
|
|
}
|
|
|
|
/* 0关闭经过时长限制,不允许正在等待的状态依赖倒退的单调时间继续执行。 */
|
|
static bool Clock_Reversed(const IEC104_Command_t *self, uint64_t now)
|
|
{
|
|
const CommandTransaction *task = &self->transaction;
|
|
return !self->in_action && task->occupied && now < task->timer_start_ms &&
|
|
(task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT || task->phase == IEC104_COMMAND_PHASE_SELECTED ||
|
|
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT ||
|
|
(task->phase == IEC104_COMMAND_PHASE_EXECUTING && task->result == 0U));
|
|
}
|
|
|
|
/* 0是未排队哨兵;耗尽时终结,绝不回绕到可能与旧排队身份相同的序号。 */
|
|
static uint64_t Reply_Serial(IEC104_Command_t *self)
|
|
{
|
|
if (self->next_reply == UINT64_MAX) {
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
|
|
self->status.active = false;
|
|
return 0U;
|
|
}
|
|
return ++self->next_reply;
|
|
}
|
|
|
|
static bool Set_Confirm(IEC104_Command_t *self, uint8_t cause, bool negative, bool try_send)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
uint64_t serial = Reply_Serial(self);
|
|
if (serial == 0U) { return false; }
|
|
task->confirm_serial = serial;
|
|
task->confirm_cot = cause;
|
|
task->confirm_negative = negative;
|
|
task->confirm_try = try_send;
|
|
task->confirm_submitted = false;
|
|
task->send_waiting = false;
|
|
return true;
|
|
}
|
|
|
|
/* 只在已处理会话门禁的调用中使用。I帧在FIFO受理时也必须先检查,避免过期选择复活。 */
|
|
static void Expire(IEC104_Command_t *self, uint64_t now)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
uint32_t limit = Timer_Limit(self);
|
|
bool reversed = Clock_Reversed(self, now);
|
|
if (!reversed && (limit == 0U || Remaining(task, limit, now) != 0U)) { return; }
|
|
if (reversed) { ++self->status.clock_errors; }
|
|
switch (task->phase) {
|
|
case IEC104_COMMAND_PHASE_SELECT_WAIT:
|
|
case IEC104_COMMAND_PHASE_EXECUTE_WAIT:
|
|
if (Set_Confirm(self, IEC60870_ASDU_COT_ACTIVATION_CON, true, false)) {
|
|
Finish(self, IEC104_COMMAND_FINISH_DECISION_TIMEOUT);
|
|
}
|
|
break;
|
|
case IEC104_COMMAND_PHASE_SELECTED:
|
|
task->confirm_serial = 0U; /* 只能撤回模块尚持有的确认。 */
|
|
Finish(self, IEC104_COMMAND_FINISH_SELECT_TIMEOUT);
|
|
break;
|
|
case IEC104_COMMAND_PHASE_EXECUTING:
|
|
/* 动作已启动,不能因结果超时把原肯定确认改成否定。 */
|
|
Finish(self, IEC104_COMMAND_FINISH_EXECUTE_TIMEOUT);
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
/* 提交入口可能接收STOP,ERROR只能在同连接健康且通知待处理时解释为等待。 */
|
|
static bool Defer_For_Stop(IEC104_Command_t *self, const IEC104_SlaveEventIdentity_t *identity,
|
|
IEC104_TcpSubmitResult_t submitted)
|
|
{
|
|
IEC104_TcpServerStatus_t status;
|
|
if (submitted != IEC104_TCP_SUBMIT_ERROR || identity->slave != self->slave ||
|
|
identity->instance_id != self->session.instance_id || identity->generation != self->session.generation ||
|
|
IEC104_Slave_Get_Status(self->slave, &status, NULL) != IEC104_TCP_OK ||
|
|
status.generation != identity->generation || !status.connected || !status.connection_status_valid ||
|
|
!status.asdu_queue_status_valid || status.connection.phase != IEC104_TCP_SESSION_ACTIVE ||
|
|
status.connection.fault != IEC104_TCP_FAULT_NONE || status.connection.data_transfer_started ||
|
|
status.pending_event_count == 0U) { return false; }
|
|
self->status.events_pending = true;
|
|
return true;
|
|
}
|
|
|
|
/* 三种事务内回复和独立回复共享一个序号域,不另建可溢出的指针FIFO。 */
|
|
enum { COMMAND_HEAD_NONE, COMMAND_HEAD_CONFIRM, COMMAND_HEAD_TERM, COMMAND_HEAD_REPLY, COMMAND_HEAD_SHORT };
|
|
static unsigned Reply_Head(const IEC104_Command_t *self, unsigned *slot)
|
|
{
|
|
const CommandTransaction *task = &self->transaction;
|
|
uint64_t first = 0U;
|
|
unsigned head = COMMAND_HEAD_NONE, i;
|
|
if (task->confirm_serial != 0U) { first = task->confirm_serial; head = COMMAND_HEAD_CONFIRM; }
|
|
if (task->term_serial != 0U && (first == 0U || task->term_serial < first)) {
|
|
first = task->term_serial; head = COMMAND_HEAD_TERM;
|
|
}
|
|
if (task->reply_serial != 0U && (first == 0U || task->reply_serial < first)) {
|
|
first = task->reply_serial; head = COMMAND_HEAD_REPLY;
|
|
}
|
|
for (i = 0U; i < IEC104_COMMAND_REPLY_CAPACITY; ++i) {
|
|
if (self->replies[i].occupied && (first == 0U || self->replies[i].serial < first)) {
|
|
first = self->replies[i].serial; head = COMMAND_HEAD_SHORT; *slot = i;
|
|
}
|
|
}
|
|
return head;
|
|
}
|
|
|
|
/* Complete关闭返回阶段后,确认和可选TERM均交接才向应用报告协议完成。 */
|
|
static void Complete_If_Sent(IEC104_Command_t *self)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
if (task->occupied && task->phase == IEC104_COMMAND_PHASE_EXECUTING && task->completed &&
|
|
task->confirm_submitted && task->confirm_serial == 0U && task->term_serial == 0U) {
|
|
Finish(self, task->result == IEC104_COMMAND_RESULT_SUCCEEDED ?
|
|
IEC104_COMMAND_FINISH_COMPLETED : IEC104_COMMAND_FINISH_EXECUTION_FAILED);
|
|
}
|
|
Reap(self);
|
|
}
|
|
|
|
/* 只提交一个队首。1有进展,0让出,-1组包/服务错误;发送故障已清理后返回0。 */
|
|
static int Submit_Head(IEC104_Command_t *self)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
const IEC104_SlaveEventIdentity_t *identity;
|
|
const uint8_t *source;
|
|
uint8_t bytes[10], cause;
|
|
IEC60870_Asdu_t asdu;
|
|
IEC104_TcpSubmitResult_t submitted;
|
|
IEC104_SlaveRequestState_t state = Check_Session(self);
|
|
unsigned slot = 0U, head;
|
|
bool negative, try_send;
|
|
if (state == IEC104_SLAVE_REQUEST_ERROR) { return -1; }
|
|
if (state != IEC104_SLAVE_REQUEST_READY) { return 0; }
|
|
head = Reply_Head(self, &slot);
|
|
if (head == COMMAND_HEAD_NONE) { return 0; }
|
|
identity = &task->identity;
|
|
source = task->request_bytes;
|
|
cause = IEC60870_ASDU_COT_ACTIVATION_TERMINATION;
|
|
negative = false;
|
|
try_send = true;
|
|
if (head == COMMAND_HEAD_CONFIRM) {
|
|
cause = task->confirm_cot; negative = task->confirm_negative; try_send = task->confirm_try;
|
|
} else if (head == COMMAND_HEAD_REPLY) {
|
|
source = task->reply_bytes; cause = task->reply_cot;
|
|
negative = cause != IEC60870_ASDU_COT_DEACTIVATION_CON; try_send = false;
|
|
} else if (head == COMMAND_HEAD_SHORT) {
|
|
identity = &self->replies[slot].identity;
|
|
source = self->replies[slot].bytes; cause = self->replies[slot].cot;
|
|
negative = true; try_send = false;
|
|
}
|
|
memcpy(bytes, source, sizeof(bytes));
|
|
if (IEC60870_Asdu_Create_From_Buffer(&asdu, IEC104_Slave_Get_Application_Layer_Parameters(self->slave),
|
|
bytes, sizeof(bytes)) != IEC60870_ASDU_OK ||
|
|
IEC60870_Asdu_Set_COT(&asdu, cause) != IEC60870_ASDU_OK ||
|
|
IEC60870_Asdu_Set_Negative(&asdu, negative) != IEC60870_ASDU_OK) {
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
|
|
return -1;
|
|
}
|
|
submitted = try_send ? IEC104_Slave_Try_Send_Response(self->slave, identity, &asdu) :
|
|
IEC104_Slave_Send_Response(self->slave, identity, &asdu);
|
|
if (submitted == IEC104_TCP_SUBMIT_OK) {
|
|
++self->status.submitted;
|
|
if (negative) { ++self->status.negative_submitted; }
|
|
if (head == COMMAND_HEAD_SHORT) { memset(&self->replies[slot], 0, sizeof(self->replies[slot])); }
|
|
else {
|
|
task->send_waiting = false;
|
|
if (head == COMMAND_HEAD_CONFIRM) { task->confirm_serial = 0U; task->confirm_submitted = true; }
|
|
else if (head == COMMAND_HEAD_TERM) { task->term_serial = 0U; task->term_submitted = true; }
|
|
else { task->reply_serial = 0U; }
|
|
}
|
|
Complete_If_Sent(self);
|
|
return 1;
|
|
}
|
|
if (submitted == IEC104_TCP_SUBMIT_WAIT) {
|
|
if (head == COMMAND_HEAD_SHORT) { self->replies[slot].send_waiting = true; }
|
|
else { task->send_waiting = true; }
|
|
++self->status.waits;
|
|
return 0;
|
|
}
|
|
if (Defer_For_Stop(self, identity, submitted)) { return 0; }
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_SEND_ERROR);
|
|
self->status.active = false;
|
|
return 0;
|
|
}
|
|
|
|
/* 新短回复不能抢占活动事务的确认存储;池满丢新,不覆盖旧回复,也不断开连接。 */
|
|
static bool Short_Reply(IEC104_Command_t *self, const IEC104_SlaveEventIdentity_t *identity,
|
|
const uint8_t *data, uint8_t cause)
|
|
{
|
|
unsigned slot;
|
|
bool empty = Pending_Replies(self) == 0U;
|
|
uint64_t serial;
|
|
for (slot = 0U; slot < IEC104_COMMAND_REPLY_CAPACITY; ++slot) {
|
|
if (!self->replies[slot].occupied) { break; }
|
|
}
|
|
if (slot == IEC104_COMMAND_REPLY_CAPACITY) {
|
|
++self->status.overloads;
|
|
++self->status.dropped_requests;
|
|
return true;
|
|
}
|
|
serial = Reply_Serial(self);
|
|
if (serial == 0U) { return false; }
|
|
self->replies[slot].identity = *identity;
|
|
memcpy(self->replies[slot].bytes, data, sizeof(self->replies[slot].bytes));
|
|
self->replies[slot].bytes[1] = 1U;
|
|
self->replies[slot].serial = serial;
|
|
self->replies[slot].cot = cause;
|
|
self->replies[slot].occupied = true;
|
|
self->replies[slot].send_waiting = false;
|
|
return !empty || Submit_Head(self) >= 0;
|
|
}
|
|
|
|
/* 替换当前工作请求,仅匹配执行可从选择走到这里。事务身份和选择资源标志保持不变。 */
|
|
static void Work_Event(IEC104_Command_t *self, const uint8_t *data, uint64_t receive_serial,
|
|
uint64_t now, bool selected)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
memcpy(task->request_bytes, data, sizeof(task->request_bytes));
|
|
task->request_bytes[1] = 1U;
|
|
task->receive_serial = receive_serial;
|
|
task->accepted_ms = task->timer_start_ms = now;
|
|
task->event_type = (data[9] & 0x80U) != 0U ? IEC104_COMMAND_SELECT : IEC104_COMMAND_EXECUTE;
|
|
task->phase = task->event_type == IEC104_COMMAND_SELECT ?
|
|
IEC104_COMMAND_PHASE_SELECT_WAIT : IEC104_COMMAND_PHASE_EXECUTE_WAIT;
|
|
task->event_serial = ++self->next_event;
|
|
task->accepted_event_serial = 0U;
|
|
if (task->event_type == IEC104_COMMAND_SELECT) { task->select_event_serial = task->event_serial; }
|
|
else { task->execute_event_serial = task->event_serial; }
|
|
task->event_taken = false;
|
|
task->selected = selected;
|
|
task->confirm_submitted = false;
|
|
task->send_waiting = false;
|
|
++self->status.accepted;
|
|
}
|
|
|
|
/* 撤销和不匹配使用本事务预留的短回复存储,不依赖独立池空闲。
|
|
* 原工作请求不覆盖,FINISHED仍能标识需要清理的选择/输出。 */
|
|
static bool End_With_Reply(IEC104_Command_t *self, const uint8_t *data, uint8_t cause,
|
|
IEC104_CommandFinishReason_t reason)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
uint64_t serial = Reply_Serial(self);
|
|
if (serial == 0U) { return false; }
|
|
task->confirm_serial = task->term_serial = 0U;
|
|
memcpy(task->reply_bytes, data, sizeof(task->reply_bytes));
|
|
task->reply_bytes[1] = 1U;
|
|
task->reply_cot = cause;
|
|
task->reply_serial = serial;
|
|
task->send_waiting = false;
|
|
Finish(self, reason);
|
|
return Submit_Head(self) >= 0;
|
|
}
|
|
|
|
/* 按COT、CA、IOA、对象的顺序判错。完整10字节仍可规范回显,截短或多对象不能伪造回显。 */
|
|
static IEC104_CommandEventResult_t Request(IEC104_Command_t *self, const IEC104_SlaveEvent_t *event,
|
|
const IEC104_SlaveEventIdentity_t *identity)
|
|
{
|
|
CommandTransaction *task = &self->transaction;
|
|
IEC60870_AsduInfo_t info;
|
|
IEC104_Station_t *station;
|
|
const uint8_t *data;
|
|
uint16_t length;
|
|
uint8_t cause = 0U, state;
|
|
uint32_t ioa;
|
|
uint64_t now;
|
|
bool same_point, is_select;
|
|
if (!self->status.active || event->asdu_result != IEC60870_ASDU_OK ||
|
|
IEC60870_Asdu_Get_Data(&event->asdu, &data, &length) != IEC60870_ASDU_OK || length == 0U ||
|
|
(data[0] != 45U && data[0] != 46U) ||
|
|
(self->config.type_mask & IEC104_COMMAND_TYPE_BIT(data[0])) == 0U ||
|
|
!IEC60870_Parse_ASDU_Header(event->asdu.parameters, data, length, &info)) {
|
|
return IEC104_COMMAND_EVENT_IGNORED;
|
|
}
|
|
now = IEC104_Net_Now_Ms();
|
|
Expire(self, now);
|
|
if (info.object_count != 1U || info.information_objects_len < 4U) {
|
|
++self->status.rejected;
|
|
return IEC104_COMMAND_EVENT_HANDLED;
|
|
}
|
|
ioa = Read_IOA(data);
|
|
station = IEC104_Slave_Get_Station(self->slave, info.common_address);
|
|
state = data[9] & 3U;
|
|
is_select = (data[9] & 0x80U) != 0U;
|
|
if (info.cause_of_transmission != IEC60870_ASDU_COT_ACTIVATION &&
|
|
info.cause_of_transmission != IEC60870_ASDU_COT_DEACTIVATION) {
|
|
cause = IEC60870_ASDU_COT_UNKNOWN_CAUSE;
|
|
} else if (station == NULL) { cause = IEC60870_ASDU_COT_UNKNOWN_COMMON_ADDRESS; }
|
|
else if (ioa == 0U) { cause = IEC60870_ASDU_COT_UNKNOWN_IOA; }
|
|
else if (info.is_sequence || length != 10U || info.is_negative_confirm || info.is_test ||
|
|
(data[0] == 45U ? (data[9] & 2U) != 0U : (state == 0U || state == 3U))) {
|
|
cause = info.cause_of_transmission == IEC60870_ASDU_COT_DEACTIVATION ?
|
|
IEC60870_ASDU_COT_DEACTIVATION_CON : IEC60870_ASDU_COT_ACTIVATION_CON;
|
|
}
|
|
if (cause != 0U) {
|
|
++self->status.rejected;
|
|
return Short_Reply(self, identity, data, cause) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
|
}
|
|
same_point = task->occupied && task->identity.generation == identity->generation &&
|
|
IEC104_Station_Get_Id(task->station) == IEC104_Station_Get_Id(station) &&
|
|
Read_IOA(task->request_bytes) == ioa;
|
|
if (info.cause_of_transmission == IEC60870_ASDU_COT_DEACTIVATION) {
|
|
if (same_point && task->request_bytes[0] == data[0] && task->request_bytes[3] == data[3] &&
|
|
(task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT || task->phase == IEC104_COMMAND_PHASE_SELECTED ||
|
|
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT)) {
|
|
return End_With_Reply(self, data, IEC60870_ASDU_COT_DEACTIVATION_CON,
|
|
IEC104_COMMAND_FINISH_DEACTIVATED) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
|
}
|
|
++self->status.rejected;
|
|
return Short_Reply(self, identity, data, IEC60870_ASDU_COT_DEACTIVATION_CON) ?
|
|
IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
|
}
|
|
/* 选择确认尚未交接时一律忙拒绝;确认交接后同点执行先匹配,其余新激活不影响选择。 */
|
|
if (same_point && !is_select && task->phase == IEC104_COMMAND_PHASE_SELECTED && task->confirm_submitted) {
|
|
if (task->request_bytes[0] != data[0] || task->request_bytes[3] != data[3] ||
|
|
(task->request_bytes[9] & 0x7FU) != (data[9] & 0x7FU)) {
|
|
++self->status.rejected;
|
|
return End_With_Reply(self, data, IEC60870_ASDU_COT_ACTIVATION_CON,
|
|
IEC104_COMMAND_FINISH_MISMATCH) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
|
}
|
|
Work_Event(self, data, event->transport.data.i_frame.receive_serial, now, true);
|
|
return IEC104_COMMAND_EVENT_HANDLED;
|
|
}
|
|
if (task->occupied || (!is_select && (self->config.select_required_mask & IEC104_COMMAND_TYPE_BIT(data[0])) != 0U)) {
|
|
++self->status.rejected;
|
|
if (task->occupied) { ++self->status.busy_rejected; }
|
|
return Short_Reply(self, identity, data, IEC60870_ASDU_COT_ACTIVATION_CON) ?
|
|
IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
|
|
}
|
|
if (self->next_transaction == UINT64_MAX || self->next_event > UINT64_MAX - 3U) {
|
|
++self->status.overloads;
|
|
++self->status.dropped_requests;
|
|
return IEC104_COMMAND_EVENT_HANDLED;
|
|
}
|
|
if (IEC104_Slave_Acquire_Station(self->slave, station) != IEC104_STATION_OK) { return IEC104_COMMAND_EVENT_ERROR; }
|
|
memset(task, 0, sizeof(*task));
|
|
task->occupied = true;
|
|
task->identity = *identity;
|
|
task->station = station;
|
|
task->transaction_id = ++self->next_transaction;
|
|
Work_Event(self, data, event->transport.data.i_frame.receive_serial, now, false);
|
|
return IEC104_COMMAND_EVENT_HANDLED;
|
|
}
|
|
|
|
void IEC104_Command_Config_Default(IEC104_CommandConfig_t *config)
|
|
{
|
|
if (config == NULL) { return; }
|
|
memset(config, 0, sizeof(*config));
|
|
config->type_mask = IEC104_COMMAND_TYPE_BIT(45U) | IEC104_COMMAND_TYPE_BIT(46U);
|
|
config->select_required_mask = config->type_mask;
|
|
config->select_timeout_ms = 30000U;
|
|
config->decision_timeout_ms = 15000U;
|
|
config->execute_timeout_ms = 60000U;
|
|
config->send_act_term = true;
|
|
}
|
|
|
|
IEC104_Command_t *IEC104_Command_Create(IEC104_Slave_t *slave, const IEC104_CommandConfig_t *config)
|
|
{
|
|
const uint16_t supported = IEC104_COMMAND_TYPE_BIT(45U) | IEC104_COMMAND_TYPE_BIT(46U);
|
|
IEC104_Command_t *free_slot = NULL;
|
|
unsigned i;
|
|
if (config == NULL || config->type_mask == 0U || (config->type_mask & (uint16_t)~supported) != 0U ||
|
|
(config->select_required_mask & (uint16_t)~config->type_mask) != 0U || config->select_timeout_ms == 0U ||
|
|
next_instance == UINT64_MAX || IEC104_Slave_Get_Application_Layer_Parameters(slave) == NULL) { return NULL; }
|
|
for (i = 0U; i < IEC104_COMMAND_CAPACITY; ++i) {
|
|
if (command_pool[i].in_use && command_pool[i].slave == slave) { return NULL; }
|
|
if (!command_pool[i].in_use && free_slot == NULL) { free_slot = &command_pool[i]; }
|
|
}
|
|
if (free_slot == NULL) { return NULL; }
|
|
memset(free_slot, 0, sizeof(*free_slot));
|
|
free_slot->slave = slave;
|
|
free_slot->config = *config;
|
|
free_slot->serial = ++next_instance;
|
|
free_slot->in_use = true;
|
|
return free_slot;
|
|
}
|
|
|
|
bool IEC104_Command_Destroy(IEC104_Command_t *self)
|
|
{
|
|
if (!Owned(self) || self->in_action || self->transaction.occupied || Pending_Replies(self) != 0U) { return false; }
|
|
memset(self, 0, sizeof(*self));
|
|
return true;
|
|
}
|
|
|
|
void IEC104_Command_Cancel(IEC104_Command_t *self)
|
|
{
|
|
if (Owned(self) && !self->in_action) { Cancel_All(self, IEC104_COMMAND_FINISH_CANCELLED); }
|
|
}
|
|
|
|
/* 状态通知先处理,不能统一套Check_Request;后续尚未出队的STOP不应阻挡当前通知消费。 */
|
|
IEC104_CommandEventResult_t IEC104_Command_Handle_Event(IEC104_Command_t *self,
|
|
const IEC104_SlaveEvent_t *event)
|
|
{
|
|
IEC104_SlaveEventIdentity_t identity;
|
|
if (!Owned(self) || self->in_action ||
|
|
!IEC104_Slave_Get_Event_Identity(self->slave, event, &identity) ||
|
|
(self->session.instance_id != 0U && self->session.instance_id != identity.instance_id)) {
|
|
return IEC104_COMMAND_EVENT_ERROR;
|
|
}
|
|
self->session.slave = identity.slave;
|
|
self->session.instance_id = identity.instance_id;
|
|
if (event->transport.type == IEC104_TCP_EVENT_CONNECTED) {
|
|
if (identity.generation <= self->last_generation) { return IEC104_COMMAND_EVENT_IGNORED; }
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_CONNECTION_CHANGED);
|
|
self->session.generation = self->last_generation = identity.generation;
|
|
self->status.active = false;
|
|
return IEC104_COMMAND_EVENT_HANDLED;
|
|
}
|
|
if (identity.generation != self->session.generation || identity.generation == 0U) {
|
|
return IEC104_COMMAND_EVENT_IGNORED;
|
|
}
|
|
switch (event->transport.type) {
|
|
case IEC104_TCP_EVENT_ACTIVATED:
|
|
self->status.active = true;
|
|
return IEC104_COMMAND_EVENT_HANDLED;
|
|
case IEC104_TCP_EVENT_DEACTIVATED:
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_STOPPED);
|
|
self->status.active = false;
|
|
return IEC104_COMMAND_EVENT_HANDLED;
|
|
case IEC104_TCP_EVENT_DISCONNECTED:
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_DISCONNECTED);
|
|
self->session.generation = 0U;
|
|
self->status.active = false;
|
|
return IEC104_COMMAND_EVENT_HANDLED;
|
|
case IEC104_TCP_EVENT_I_FRAME: return Request(self, event, &identity);
|
|
default: return IEC104_COMMAND_EVENT_IGNORED;
|
|
}
|
|
}
|
|
|
|
bool IEC104_Command_Process(IEC104_Command_t *self)
|
|
{
|
|
unsigned step;
|
|
if (!Owned(self) || self->in_action) { return false; }
|
|
for (step = 0U; step < 2U * (1U + IEC104_COMMAND_REPLY_CAPACITY); ++step) {
|
|
IEC104_SlaveRequestState_t state = Check_Session(self);
|
|
int submitted;
|
|
if (state == IEC104_SLAVE_REQUEST_ERROR) { return false; }
|
|
if (state != IEC104_SLAVE_REQUEST_READY) { return true; }
|
|
Expire(self, IEC104_Net_Now_Ms());
|
|
Complete_If_Sent(self);
|
|
submitted = Submit_Head(self);
|
|
if (submitted <= 0) { return submitted == 0; }
|
|
}
|
|
return true;
|
|
}
|
|
|
|
IEC104_CommandNextResult_t IEC104_Command_NextEvent(IEC104_Command_t *self, IEC104_CommandEvent_t *event)
|
|
{
|
|
CommandTransaction *task;
|
|
IEC104_CommandEvent_t result;
|
|
IEC104_SlaveRequestState_t state = IEC104_SLAVE_REQUEST_READY;
|
|
if (!Owned(self) || self->in_action || event == NULL) { return IEC104_COMMAND_NEXT_ERROR; }
|
|
task = &self->transaction;
|
|
/* 已存在的FINISHED不受离线或未读通知门禁阻挡;它只清理已结束的协议交互。 */
|
|
if (!task->finish_pending) {
|
|
state = Check_Session(self);
|
|
if (state == IEC104_SLAVE_REQUEST_READY) { Expire(self, IEC104_Net_Now_Ms()); }
|
|
}
|
|
if (!task->finish_pending && state == IEC104_SLAVE_REQUEST_ERROR) { return IEC104_COMMAND_NEXT_ERROR; }
|
|
if (!task->occupied || (!task->finish_pending &&
|
|
(state != IEC104_SLAVE_REQUEST_READY || task->event_taken ||
|
|
(task->phase != IEC104_COMMAND_PHASE_SELECT_WAIT && task->phase != IEC104_COMMAND_PHASE_EXECUTE_WAIT)))) {
|
|
return IEC104_COMMAND_NEXT_EMPTY;
|
|
}
|
|
memset(&result, 0, sizeof(result));
|
|
result.type = (IEC104_CommandEventType_t)task->event_type;
|
|
result.instance_id = self->serial;
|
|
result.transaction_id = task->transaction_id;
|
|
result.event_serial = task->event_serial;
|
|
Copy_Request(task, &result.request);
|
|
if (task->finish_pending) {
|
|
result.reason = (IEC104_CommandFinishReason_t)task->finish_reason;
|
|
result.select_effect = task->select_effect;
|
|
result.execute_effect = task->execute_effect;
|
|
task->finish_pending = false;
|
|
} else {
|
|
task->ever_work_delivered = true;
|
|
if (result.type == IEC104_COMMAND_SELECT) { task->select_delivered = true; }
|
|
else { task->execute_delivered = true; }
|
|
}
|
|
task->event_taken = true;
|
|
*event = result;
|
|
Reap(self);
|
|
return IEC104_COMMAND_NEXT_READY;
|
|
}
|
|
|
|
/* 校验应用交互的独立身份;Slave身份只解决会话归属,不能代替事务及工作事件序号。 */
|
|
static bool Current_Event(const IEC104_Command_t *self, const IEC104_CommandEvent_t *event)
|
|
{
|
|
const CommandTransaction *task = &self->transaction;
|
|
return event != NULL && task->occupied && event->instance_id == self->serial &&
|
|
event->transaction_id == task->transaction_id && event->event_serial == task->event_serial &&
|
|
event->type == task->event_type && task->event_taken;
|
|
}
|
|
|
|
/* work=true用于未消费的SELECT/EXECUTE;false用于启动成功后可多次提交返回信息的EXECUTE令牌。 */
|
|
static bool Reply_Stage(const IEC104_Command_t *self, const IEC104_CommandEvent_t *event, bool work)
|
|
{
|
|
const CommandTransaction *task = &self->transaction;
|
|
if (!Current_Event(self, event)) { return false; }
|
|
if (work) {
|
|
return task->accepted_event_serial == 0U &&
|
|
((event->type == IEC104_COMMAND_SELECT && task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT) ||
|
|
(event->type == IEC104_COMMAND_EXECUTE && task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT));
|
|
}
|
|
return event->type == IEC104_COMMAND_EXECUTE && task->phase == IEC104_COMMAND_PHASE_EXECUTING &&
|
|
task->accepted_event_serial == event->event_serial && task->execute_effect;
|
|
}
|
|
|
|
static IEC104_CommandReplyResult_t Check_Reply(IEC104_Command_t *self,
|
|
const IEC104_CommandEvent_t *event, bool work)
|
|
{
|
|
IEC104_SlaveRequestState_t state;
|
|
if (!Owned(self) || self->in_action || event == NULL) { return IEC104_COMMAND_REPLY_ERROR; }
|
|
if (!Reply_Stage(self, event, work)) { return IEC104_COMMAND_REPLY_STALE; }
|
|
state = Check_Session(self);
|
|
if (state == IEC104_SLAVE_REQUEST_ERROR) { return IEC104_COMMAND_REPLY_ERROR; }
|
|
if (state == IEC104_SLAVE_REQUEST_WAIT_EVENTS) { return IEC104_COMMAND_REPLY_WAIT; }
|
|
if (state != IEC104_SLAVE_REQUEST_READY) { return IEC104_COMMAND_REPLY_STALE; }
|
|
Expire(self, IEC104_Net_Now_Ms());
|
|
return Reply_Stage(self, event, work) ? IEC104_COMMAND_REPLY_OK : IEC104_COMMAND_REPLY_STALE;
|
|
}
|
|
|
|
IEC104_CommandReplyResult_t IEC104_Command_Accept(IEC104_Command_t *self,
|
|
const IEC104_CommandEvent_t *event, IEC104_CommandAction_t action, void *context)
|
|
{
|
|
CommandTransaction *task;
|
|
IEC104_CommandRequest_t request;
|
|
IEC104_CommandActionResult_t action_result;
|
|
IEC104_CommandReplyResult_t checked;
|
|
bool selecting;
|
|
if (event != NULL && event->type == IEC104_COMMAND_EXECUTE && action == NULL) {
|
|
return IEC104_COMMAND_REPLY_ERROR;
|
|
}
|
|
checked = Check_Reply(self, event, true);
|
|
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
|
task = &self->transaction;
|
|
selecting = task->event_type == IEC104_COMMAND_SELECT;
|
|
/* 确认资源和序号在动作前准备。事务自己的回复不会被独立短回复洪泛挤占。 */
|
|
if (!Set_Confirm(self, IEC60870_ASDU_COT_ACTIVATION_CON, false, !selecting)) {
|
|
return IEC104_COMMAND_REPLY_ERROR;
|
|
}
|
|
Copy_Request(task, &request);
|
|
task->accepted_event_serial = task->event_serial;
|
|
self->in_action = true;
|
|
action_result = action == NULL ? IEC104_COMMAND_ACTION_STARTED : action(context, &request);
|
|
self->in_action = false;
|
|
/* 非法动作返回值按可能有副作用的失败处理;已消费的动作绝不重试。 */
|
|
if (action_result != IEC104_COMMAND_ACTION_STARTED && action_result != IEC104_COMMAND_ACTION_NOT_STARTED) {
|
|
action_result = IEC104_COMMAND_ACTION_FAILED;
|
|
}
|
|
if (selecting) { task->select_effect = action != NULL && action_result != IEC104_COMMAND_ACTION_NOT_STARTED; }
|
|
else { task->execute_effect = action_result != IEC104_COMMAND_ACTION_NOT_STARTED; }
|
|
if (action_result == IEC104_COMMAND_ACTION_STARTED) {
|
|
task->phase = selecting ? IEC104_COMMAND_PHASE_SELECTED : IEC104_COMMAND_PHASE_EXECUTING;
|
|
task->timer_start_ms = IEC104_Net_Now_Ms();
|
|
if (selecting) { ++self->status.selected; }
|
|
else { ++self->status.executed; }
|
|
} else {
|
|
task->confirm_negative = true;
|
|
task->confirm_try = !selecting && action_result == IEC104_COMMAND_ACTION_FAILED;
|
|
if (action_result == IEC104_COMMAND_ACTION_NOT_STARTED) {
|
|
++self->status.app_rejected;
|
|
Finish(self, IEC104_COMMAND_FINISH_REJECTED);
|
|
} else { Finish(self, IEC104_COMMAND_FINISH_ACTION_FAILED); }
|
|
}
|
|
/* 提交可能接收STOP或产生故障;OK仍只描述动作交互已消费,应用继续读取FINISHED。 */
|
|
(void)Submit_Head(self);
|
|
return IEC104_COMMAND_REPLY_OK;
|
|
}
|
|
|
|
IEC104_CommandReplyResult_t IEC104_Command_Reject(IEC104_Command_t *self,
|
|
const IEC104_CommandEvent_t *event, IEC104_CommandRejectCause_t cause)
|
|
{
|
|
IEC104_CommandReplyResult_t checked;
|
|
if (cause != IEC104_COMMAND_REJECT_NEGATIVE && cause != IEC104_COMMAND_REJECT_UNKNOWN_IOA) {
|
|
return IEC104_COMMAND_REPLY_ERROR;
|
|
}
|
|
checked = Check_Reply(self, event, true);
|
|
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
|
if (!Set_Confirm(self, cause == IEC104_COMMAND_REJECT_UNKNOWN_IOA ?
|
|
IEC60870_ASDU_COT_UNKNOWN_IOA : IEC60870_ASDU_COT_ACTIVATION_CON, true, false)) {
|
|
return IEC104_COMMAND_REPLY_ERROR;
|
|
}
|
|
self->transaction.accepted_event_serial = self->transaction.event_serial;
|
|
++self->status.app_rejected;
|
|
Finish(self, IEC104_COMMAND_FINISH_REJECTED);
|
|
(void)Submit_Head(self);
|
|
return IEC104_COMMAND_REPLY_OK;
|
|
}
|
|
|
|
IEC104_CommandReplyResult_t IEC104_Command_Report_Result(IEC104_Command_t *self,
|
|
const IEC104_CommandEvent_t *event, IEC104_CommandResult_t result)
|
|
{
|
|
IEC104_CommandReplyResult_t checked;
|
|
if (result != IEC104_COMMAND_RESULT_SUCCEEDED && result != IEC104_COMMAND_RESULT_FAILED) {
|
|
return IEC104_COMMAND_REPLY_ERROR;
|
|
}
|
|
checked = Check_Reply(self, event, false);
|
|
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
|
if (self->transaction.result != 0U || self->transaction.completed) { return IEC104_COMMAND_REPLY_ERROR; }
|
|
self->transaction.result = (uint8_t)result;
|
|
return IEC104_COMMAND_REPLY_OK;
|
|
}
|
|
|
|
IEC104_CommandReplyResult_t IEC104_Command_Submit_Return_Info(IEC104_Command_t *self,
|
|
const IEC104_CommandEvent_t *event, const IEC60870_Asdu_t *asdu)
|
|
{
|
|
CommandTransaction *task;
|
|
IEC60870_AsduInfo_t info;
|
|
const uint8_t *data;
|
|
uint16_t length;
|
|
IEC104_TcpSubmitResult_t submitted;
|
|
IEC104_CommandReplyResult_t checked = Check_Reply(self, event, false);
|
|
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
|
task = &self->transaction;
|
|
if (task->completed || asdu == NULL || !IEC60870_AppLayerParameters_Valid(asdu->parameters) ||
|
|
asdu->parameters->sizeOfCOT != 2 || asdu->parameters->sizeOfCA != 2 || asdu->parameters->sizeOfIOA != 3 ||
|
|
asdu->parameters->maxSizeOfASDU > IEC104_MAX_ASDU_LEN ||
|
|
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK || length > IEC104_MAX_ASDU_LEN ||
|
|
IEC60870_Asdu_Parse(IEC104_Slave_Get_Application_Layer_Parameters(self->slave), data, length, &info) !=
|
|
IEC60870_ASDU_OK ||
|
|
!((info.type_id >= 1U && info.type_id <= 21U) || (info.type_id >= 30U && info.type_id <= 40U)) ||
|
|
info.object_count == 0U ||
|
|
(info.cause_of_transmission != IEC60870_ASDU_COT_RETURN_INFO_REMOTE &&
|
|
info.cause_of_transmission != IEC60870_ASDU_COT_SPONTANEOUS) ||
|
|
info.is_negative_confirm || info.is_test != ((task->request_bytes[2] >> 7) & 1U) ||
|
|
info.common_address != Read_CA(task->request_bytes)) { return IEC104_COMMAND_REPLY_ERROR; }
|
|
/* 底层Try只知道传输队列;这里还必须检查模块内更早的独立/事务应答。 */
|
|
if (!task->confirm_submitted || Pending_Replies(self) != 0U) { return IEC104_COMMAND_REPLY_WAIT; }
|
|
submitted = IEC104_Slave_Try_Send_Response(self->slave, &task->identity, asdu);
|
|
if (submitted == IEC104_TCP_SUBMIT_WAIT) {
|
|
task->return_waiting = true;
|
|
++self->status.waits;
|
|
return IEC104_COMMAND_REPLY_WAIT;
|
|
}
|
|
if (submitted == IEC104_TCP_SUBMIT_OK) {
|
|
task->return_waiting = false;
|
|
++self->status.submitted;
|
|
++self->status.return_info_submitted;
|
|
return IEC104_COMMAND_REPLY_OK;
|
|
}
|
|
if (Defer_For_Stop(self, &task->identity, submitted)) { return IEC104_COMMAND_REPLY_WAIT; }
|
|
Cancel_All(self, IEC104_COMMAND_FINISH_SEND_ERROR);
|
|
self->status.active = false;
|
|
return IEC104_COMMAND_REPLY_STALE;
|
|
}
|
|
|
|
IEC104_CommandReplyResult_t IEC104_Command_Complete(IEC104_Command_t *self,
|
|
const IEC104_CommandEvent_t *event)
|
|
{
|
|
CommandTransaction *task;
|
|
IEC104_CommandReplyResult_t checked = Check_Reply(self, event, false);
|
|
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
|
|
task = &self->transaction;
|
|
if (task->result == 0U || task->completed) { return IEC104_COMMAND_REPLY_ERROR; }
|
|
if (self->config.send_act_term &&
|
|
(task->result == IEC104_COMMAND_RESULT_SUCCEEDED || self->config.act_term_on_failure)) {
|
|
uint64_t serial = Reply_Serial(self);
|
|
if (serial == 0U) { return IEC104_COMMAND_REPLY_ERROR; }
|
|
task->term_serial = serial;
|
|
}
|
|
task->completed = true;
|
|
task->return_waiting = false;
|
|
/* 无TERM且确认已交接可立即结束;否则Process只重试协议收尾,不要求应用重复Complete。 */
|
|
Complete_If_Sent(self);
|
|
return IEC104_COMMAND_REPLY_OK;
|
|
}
|
|
|
|
bool IEC104_Command_Get_Transaction(const IEC104_Command_t *self,
|
|
const IEC104_CommandEvent_t *event, IEC104_CommandTransactionInfo_t *info)
|
|
{
|
|
const CommandTransaction *task;
|
|
IEC104_CommandTransactionInfo_t result;
|
|
bool delivered;
|
|
if (!Owned(self) || event == NULL || info == NULL) { return false; }
|
|
task = &self->transaction;
|
|
if (!task->occupied || event->instance_id != self->serial || event->transaction_id != task->transaction_id) {
|
|
return false;
|
|
}
|
|
delivered = (event->type == IEC104_COMMAND_SELECT && task->select_delivered &&
|
|
event->event_serial == task->select_event_serial) ||
|
|
(event->type == IEC104_COMMAND_EXECUTE && task->execute_delivered &&
|
|
event->event_serial == task->execute_event_serial) ||
|
|
(event->type == IEC104_COMMAND_FINISHED && task->event_type == IEC104_COMMAND_FINISHED &&
|
|
task->event_taken && event->event_serial == task->event_serial);
|
|
if (!delivered) { return false; }
|
|
memset(&result, 0, sizeof(result));
|
|
result.active = task->phase != IEC104_COMMAND_PHASE_ENDED;
|
|
result.phase = (IEC104_CommandPhase_t)task->phase;
|
|
result.confirm_submitted = task->confirm_submitted;
|
|
result.result_reported = task->result != 0U;
|
|
result.completed = task->completed;
|
|
result.term_submitted = task->term_submitted;
|
|
*info = result;
|
|
return true;
|
|
}
|
|
|
|
bool IEC104_Command_Get_Status(const IEC104_Command_t *self, IEC104_CommandStatus_t *status)
|
|
{
|
|
const CommandTransaction *task;
|
|
IEC104_CommandStatus_t result;
|
|
IEC104_SlaveRequestState_t state;
|
|
uint32_t limit;
|
|
uint64_t now;
|
|
unsigned slot = 0U, head;
|
|
bool waiting;
|
|
if (!Owned(self) || status == NULL) { return false; }
|
|
state = IEC104_Slave_Check_Request(self->slave, &self->session);
|
|
if (state == IEC104_SLAVE_REQUEST_ERROR) { return false; }
|
|
task = &self->transaction;
|
|
result = self->status;
|
|
result.generation = self->session.generation;
|
|
result.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
|
|
result.busy = task->occupied;
|
|
result.pending_replies = Pending_Replies(self);
|
|
result.pending_events = task->occupied && (task->finish_pending ||
|
|
(!task->event_taken && (task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT ||
|
|
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT))) ? 1U : 0U;
|
|
limit = Timer_Limit(self);
|
|
now = IEC104_Net_Now_Ms();
|
|
result.timer_active = limit != 0U;
|
|
result.timeout_remaining_ms = limit != 0U ? Remaining(task, limit, now) : 0U;
|
|
result.need_process = false;
|
|
head = Reply_Head(self, &slot);
|
|
waiting = head == COMMAND_HEAD_SHORT ? self->replies[slot].send_waiting : task->send_waiting;
|
|
if (!self->in_action && !result.events_pending) {
|
|
if (state != IEC104_SLAVE_REQUEST_READY || !result.active) {
|
|
result.need_process = result.pending_replies != 0U ||
|
|
(task->occupied && task->phase != IEC104_COMMAND_PHASE_ENDED);
|
|
} else {
|
|
result.need_process = Clock_Reversed(self, now) ||
|
|
(result.timer_active && result.timeout_remaining_ms == 0U) ||
|
|
(head != COMMAND_HEAD_NONE && !waiting);
|
|
}
|
|
}
|
|
*status = result;
|
|
return true;
|
|
}
|