Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,9 @@
由中央包管理固定版本且只从 nuget.org 还原,不引用本地源码 checkout。
- 增加双方 Active/Passive、Select、双方 Linktest、双方
Primary/Secondary 及嵌套 Item 边界值的真实 TCP 跨实现测试。
- 增加公共 <code>HsmsDiagnostic</code> 结构化诊断模型,在保留原异常的
同时分类运输、协议、会话、事务、T3/T6/T7/T8 和解码故障。
- 连接状态与解码失败事件公开可选诊断;操作异常可显式分类,调用方取消、
释放和生命周期误用不会被误报为协议故障。

[Unreleased]: https://github.com/CSJ608/SecsFrame/compare/main...HEAD
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Deselect、Separate、T6/T7 和 Selected 数据门控。内部数据事务 actor
[docs/HSMS-DATA-TRANSACTIONS.md](docs/HSMS-DATA-TRANSACTIONS.md),
公共连接用法见
[docs/HSMS-CONNECTION.md](docs/HSMS-CONNECTION.md),
结构化故障分类见
[docs/HSMS-DIAGNOSTICS.md](docs/HSMS-DIAGNOSTICS.md),
官方 secs4net 跨实现证据见
[docs/SECS4NET-INTEROP.md](docs/SECS4NET-INTEROP.md),
整体路线图见
Expand Down
11 changes: 9 additions & 2 deletions docs/HSMS-CONNECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ var secondary = await connection.SendAsync(
- <code>ControlMessageReceived</code>:未被当前控制或数据事务认领的帧;
- <code>DataMessageDecodeFailed</code>:原始帧和严格解码错误。

带错误的状态事件和解码失败事件还会提供可选
<code>HsmsDiagnostic</code>,用于按稳定代码、层级、操作与计时器处理,
无需解析异常消息。调用任务抛出的异常可通过
<code>HsmsDiagnostic.Classify</code> 使用同一分类。完整契约和原始帧的
数据安全边界见 [HSMS-DIAGNOSTICS.md](HSMS-DIAGNOSTICS.md)。

入站消息只有 <code>ReplyExpected</code> 为真时可以回复。回复自动复制原
协议 Session ID 与 System Bytes,并绑定原 transport session;开始回复
后令牌不可再次使用,也不会在替换连接上重放。
Expand All @@ -93,9 +99,10 @@ var secondary = await connection.SendAsync(
- Deselect、Separate、断线、替换会话或释放会结束绑定旧会话的等待;
- 事件枚举的取消只结束该消费者;连接仍须显式释放。

当前具体异常类型仍以 <code>TimeoutException</code>、
具体异常类型继续以 <code>TimeoutException</code>、
<code>IOException</code>、<code>OperationCanceledException</code> 和参数/
状态异常为稳定分类;更细的公共诊断模型将在互操作证据后再收敛。
状态异常为兼容分类;<code>HsmsDiagnostic</code> 在其上提供更细的机器可读
上下文。调用方取消、释放和生命周期误用不被分类成协议故障。

## 验证边界

Expand Down
77 changes: 77 additions & 0 deletions docs/HSMS-DIAGNOSTICS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# HSMS 结构化诊断

## 目标与边界

<code>HsmsDiagnostic</code> 为公共连接事件和操作异常提供稳定、机器可读的
故障上下文。应用可以按 <code>Code</code>、<code>Layer</code>、
<code>Operation</code> 和 <code>Timer</code> 做指标、告警与恢复决策,不需要
解析英文异常消息。

诊断不替换原异常:<code>HsmsConnectionEvent.Error</code> 和调用任务抛出的
异常保持原有类型层级,诊断的 <code>Error</code> 属性指向同一个异常实例。
这使现有按 <code>TimeoutException</code>、<code>IOException</code> 或
<code>OperationCanceledException</code> 处理的代码继续工作。

## 事件诊断

<code>HsmsConnectionEvent.Diagnostic</code> 在以下事件中按当前证据填充:

- 带可分类错误的 <code>StateChanged</code>;
- 所有 <code>DataMessageDecodeFailed</code>。

正常状态转换、业务消息和未消费控制消息的诊断为空。远端正常关闭但没有
运输错误的状态转换也不会被强行解释成故障。

~~~csharp
await foreach (var connectionEvent in connection.GetEventsAsync(cancellationToken))
{
if (connectionEvent.Diagnostic is not { } diagnostic)
continue;

RecordMetric(
diagnostic.Code,
diagnostic.Layer,
diagnostic.Operation,
diagnostic.Timer);
}
~~~

## 操作异常分类

<code>SendAsync</code>、<code>ReplyAsync</code> 和控制命令的异常可以通过
<code>HsmsDiagnostic.Classify</code> 分类:

~~~csharp
try
{
await connection.SendAsync(primary, cancellationToken);
}
catch (Exception error)
{
var diagnostic = HsmsDiagnostic.Classify(error, connection.State);
if (diagnostic is null)
throw;

HandleHsmsFailure(diagnostic);
}
~~~

当前稳定代码覆盖运输失败/会话失效、协议错误、T3/T6/T7/T8、选择与
控制拒绝、数据拒绝、事务中断和数据消息解码失败。T3 诊断携带协议
Session ID 与 System Bytes;拒绝诊断保留远端状态或原因字节。

调用方取消、释放、参数错误、生命周期误用和未知异常返回空诊断。这些结果
不能被监控系统误记为协议或设备故障。T5 当前是 Active 连接重试节流,
不是每次失败都会产生一个独立公共诊断;<code>HsmsTimer.T5</code> 只保留
计时器身份,等待未来有明确、经过验证的可观测契约。

## 数据安全

解码失败诊断的 <code>Frame</code> 与事件中的原始帧相同,可能包含设备或
工艺数据。库不自动格式化、写日志或上传它。应用在记录前应按自己的数据
分类策略做脱敏和长度限制;常规指标优先只使用代码、层级、操作和计时器。

## 标准边界

诊断名称描述 SecsFrame 已实现并测试的工程行为,不新增 SEMI 默认值,也
不改变 E37/E37.1 状态机。诊断覆盖不代表完整标准合规或设备认证结果。
8 changes: 5 additions & 3 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
确定的取消/释放语义。
- [x] 使用 nuget.org 官方 Secs4Net 3.1.0 的 Active/Passive、Select、
双方 Linktest 和双方 Primary/Secondary 跨实现测试。
- [x] 公共结构化诊断模型,覆盖运输、协议、会话、事务和解码故障。
- [ ] 使用授权 E37/E37.1 核对 T5/T8 默认值与精确启停边界。
- [ ] Host、Equipment 双角色能力及对应回环集成测试。

阶段 1 的下一最小切片是根据跨实现证据收敛公共诊断模型,同时继续跟踪
StreamFrame #38/#39 的正式包发布并准备等价迁移。任何标准默认值和完整
合规结论都依赖团队合法获得的 E37/E37.1 副本与一致性测试。
阶段 1 的下一最小切片是运行期 Primary 路由与处理器注册,随后建立
Host/Equipment 角色能力,同时继续跟踪 StreamFrame #38/#39 的正式包
发布并准备等价迁移。任何标准默认值和完整合规结论都依赖团队合法获得的
E37/E37.1 副本与一致性测试。

## 阶段 2:GEM 通用能力

Expand Down
15 changes: 12 additions & 3 deletions src/SecsFrame/Hsms/Connection/HsmsConnectionEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ private HsmsConnectionEvent(
HsmsSessionState state,
HsmsIncomingDataMessage? incomingMessage,
HsmsFrame? frame,
Exception? error)
Exception? error,
HsmsDiagnostic? diagnostic)
{
Kind = kind;
State = state;
IncomingMessage = incomingMessage;
Frame = frame;
Error = error;
Diagnostic = diagnostic;
}

/// <summary>Gets the event kind.</summary>
Expand All @@ -32,6 +34,9 @@ private HsmsConnectionEvent(
/// <summary>Gets the state or decoding error, when applicable.</summary>
public Exception? Error { get; }

/// <summary>Gets structured failure context, when this event reports a diagnostic.</summary>
public HsmsDiagnostic? Diagnostic { get; }

internal static HsmsConnectionEvent StateChanged(
HsmsSessionState state,
Exception? error)
Expand All @@ -40,7 +45,8 @@ internal static HsmsConnectionEvent StateChanged(
state,
null,
null,
error);
error,
error is null ? null : HsmsDiagnostic.Classify(error, state));

internal static HsmsConnectionEvent DataMessageReceived(
HsmsIncomingDataMessage incomingMessage)
Expand All @@ -49,6 +55,7 @@ internal static HsmsConnectionEvent DataMessageReceived(
HsmsSessionState.Selected,
incomingMessage,
null,
null,
null);

internal static HsmsConnectionEvent ControlMessageReceived(
Expand All @@ -59,6 +66,7 @@ internal static HsmsConnectionEvent ControlMessageReceived(
state,
null,
frame,
null,
null);

internal static HsmsConnectionEvent DataMessageDecodeFailed(
Expand All @@ -69,5 +77,6 @@ internal static HsmsConnectionEvent DataMessageDecodeFailed(
HsmsSessionState.Selected,
null,
frame,
error);
error,
HsmsDiagnostic.DataMessageDecodeFailed(frame, error));
}
Loading
Loading