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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@

### 新增

- 增加默认关闭的完整 HSMS 控制消息元数据观测流,覆盖内部 Select、
Linktest、Deselect、Reject 与 Separate 的收发,而不改变现有业务事件流。
- 观测只保留方向、actor 观察状态与原十字节头;入站在协议处理前发布,
出站仅在整帧写出确认后发布,并可直接创建控制 Trace 记录。
- 增加状态机顺序、未知 SType/Reject、消费者替换和 Active/Passive 真实
TCP Select/Linktest 测试;原始字节、Body、transport generation 与时间戳
仍不进入观测模型。
- 增加独立 <code>SecsFrame-ControlTrace/1</code> 信封,对公共未认领控制
事件提供不含 Body 的十字节头元数据导出与严格读取。
- 控制记录保留方向、观察状态和原始头字段,允许未知非零 SType 往返;增加
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ Equipment 应用发送策略的 Collection Event、报警目录查询、单报
<code>SecsFrame.Trace</code> 在该文本层上提供版本化的数据消息 Trace 信封、
严格的 Item 路径脱敏、显式 allowlist 重放,以及默认关闭的缩放/封顶时序
重放;独立诊断信封只保存稳定标量,不保存异常或原始帧;控制信封可保存
未认领公共控制事件的十字节头元数据。该包不会接管连接事件消费者,也不会
复用原事务标识。
显式启用的完整控制消息观测流或既有未认领控制事件的十字节头元数据。
控制观测默认关闭,并与业务事件流独立;该包不会自行接管任何连接消费者,
也不会复用原事务标识。
Item 用法与边界见
[docs/SECS-II-ITEMS.md](docs/SECS-II-ITEMS.md),消息集成见
[docs/SECS-MESSAGES.md](docs/SECS-MESSAGES.md),传输适配边界见
Expand Down
15 changes: 11 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ Trace 的时序重放必须显式启用,只在允许发送记录之间等待
发送 API 创建新事务;默认重放不引入时间等待。
结构化诊断使用独立只读快照信封:只复制公共诊断的稳定标量,明确排除原
异常和未解码帧,也不能进入重放路径。
控制消息使用另一个只读元数据信封,只从调用方已消费的公共未认领控制事件
复制十字节头字段;它不增加内部控制面观察者,也不代表完整握手抓包。
控制消息使用另一个只读元数据信封。核心连接可显式启用独立控制观测流,
从会话 actor 复制全部收发控制消息的十字节头字段;Trace 仍由调用方消费
该流并创建记录,不自行订阅连接。既有公共未认领控制事件工厂继续可用。

## 核心原则

Expand Down Expand Up @@ -128,6 +129,11 @@ Selected 转发数据消息;提前到达的数据、可识别的不支持类
无法由会话层认领的 Reject 作为 <code>ControlMessageReceived</code>
事件向上转发,供数据事务 actor 关联,不会被静默丢弃。

可选控制观测通道也由同一 actor 写入:非 Data Message 的入站头在协议
验证和状态转换前记录,出站头只在绑定当前会话的整帧发送成功后记录。
通道默认不创建,记录不含 Body、原始字节、transport Session generation
或时间戳;它是诊断元数据边界,不参与协议决策。

### 会话绑定的数据事务

内部 <code>HsmsDataTransactionManager</code> 独占消费会话事件,并把
Expand All @@ -152,7 +158,8 @@ HSMS 头 Session ID 与 System Bytes。没有 W-Bit 的出站消息在写出后
<code>HsmsConnection</code> 是运输、会话和数据事务 actor 之上的薄公共
组合层。它要求显式网络、协议 Session ID 和 T3/T5/T6/T7/T8 配置,公开
动态消息发送、一次性入站回复、控制命令、Selected 等待和单消费者事件
流。公共层不暴露 transport Session generation、StreamFrame
流,并可显式启用与业务事件独立的单消费者控制元数据观测流。公共层不暴露
transport Session generation、StreamFrame
<code>StreamConnectionOptions</code> 或 StreamFrame 的会话感知接口。

连接内部独占消费事务事件,并分别更新状态等待信号与公共事件 channel,
Expand All @@ -172,6 +179,6 @@ HSMS 头 Session ID 与 System Bytes。没有 W-Bit 的出站消息在写出后
公共发送 API,不得直接构造或写出保留旧事务标识的线上帧;可选时序只
控制相邻发送前的等待,不得绕过连接状态、T3 或显式 allowlist。诊断
导出不得隐式包含 <code>Exception</code> 或 <code>HsmsFrame</code>;控制
元数据导出不得包含 Body 或隐式订阅连接事件流。
元数据导出不得包含 Body 或隐式订阅任何连接流。
- 核心包不得依赖 GEM、SML/SMN、依赖注入容器或具体日志实现。
- 状态机计时使用可替换的时间抽象,测试不得依赖真实长时间等待。
37 changes: 34 additions & 3 deletions docs/HSMS-CONNECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ StreamFrame 运输适配、HSMS-SS 会话 actor 和数据事务 actor,调用
- W-Bit Primary 的 T3 关联与 Secondary 返回;
- 接收入站 Primary 或未匹配消息,并使用不可伪造令牌回复一次;
- Linktest、Deselect 和 Separate 控制命令;
- 状态、数据、未消费控制消息和解码失败的单消费者异步事件流。
- 状态、数据、未消费控制消息和解码失败的单消费者异步事件流;
- 默认关闭、与业务事件独立的完整控制消息头元数据观测流。

Host/Equipment 是后续业务能力角色,不由 Active/Passive 推断。
需要按运行期 Stream/Function 处理 Primary 时,可在本事件流上组合
Expand All @@ -35,6 +36,7 @@ Host/Equipment 是后续业务能力角色,不由 Active/Passive 推断。
| T6 | Select、Linktest、Deselect 控制响应等待 |
| T7 | TCP 建立后等待进入 Selected |
| T8 | 未完成消息接收进展等待 |
| EnableControlMessageObservation | 是否创建完整控制消息元数据观测流;默认 false |

仓库没有内置或暗示标准默认值。T5 必须能无损转换成 StreamFrame 使用的
整毫秒范围;Passive 监听重试不被解释成 T5。每个取值应依据团队合法获得
Expand All @@ -52,7 +54,8 @@ var options = new HsmsConnectionOptions(
t5: TimeSpan.FromSeconds(10),
t6: TimeSpan.FromSeconds(5),
t7: TimeSpan.FromSeconds(10),
t8: TimeSpan.FromSeconds(5));
t8: TimeSpan.FromSeconds(5),
enableControlMessageObservation: true);

await using var connection = new HsmsConnection(options);
connection.Start();
Expand Down Expand Up @@ -89,6 +92,33 @@ var secondary = await connection.SendAsync(
不包含已被内部 Select、Linktest、Deselect 或数据事务认领的控制消息,
因此快照不是完整控制面抓包。格式与安全边界见 [TRACE.md](TRACE.md)。

完整控制面诊断必须在构造连接时显式设置
<code>EnableControlMessageObservation</code>,并由应用持续消费独立流:

~~~csharp
await foreach (var observation in connection
.GetControlMessageObservationsAsync(cancellationToken))
{
var record = SecsTraceControlRecord.Create(
DateTimeOffset.UtcNow,
observation);
}
~~~

该流与 <code>GetEventsAsync</code> 各自只有一个活动消费者,取消或释放旧
枚举器后可重新获取,二者互不争抢。入站控制头在会话 actor 执行协议处理和
状态转换前发布;出站控制头只在整帧写入成功后发布,因此 Select Request
通常观察为 Selecting,Passive 的成功 Select Response 仍观察为 Connected。
极快响应可能先于本地发送完成回调进入 actor,跨方向记录顺序和出站状态应
解释为 actor 实际处理顺序,而不是线上字节的精确时间顺序。
启用后通道会缓存尚未消费的记录,应用必须持续读取;默认关闭时不创建通道,
调用观测 API 会失败。

<code>HsmsControlMessageObservation</code> 只包含本地方向、观察状态和原
十字节 <code>HsmsMessageHeader</code>。它不含 Body、原始 TCP/帧字节、
transport Session generation 或时间戳,也不改变未认领
<code>ControlMessageReceived</code> 业务事件的既有行为。

带错误的状态事件和解码失败事件还会提供可选
<code>HsmsDiagnostic</code>,用于按稳定代码、层级、操作与计时器处理,
无需解析异常消息。调用任务抛出的异常可通过
Expand Down Expand Up @@ -117,4 +147,5 @@ var secondary = await connection.SendAsync(

真实 TCP 测试使用两个公共连接完成 Select、Linktest、嵌套 Item
Primary/Secondary、重复回复拒绝和取消后保持 Selected。它证明当前实现
各层能够组合工作,不替代 SEMI 一致性认证或第三方设备互操作。
各层能够组合工作;另有完整控制观测测试核对双方 Select/Linktest 的方向、
状态和头字段相等。它们不替代 SEMI 一致性认证或第三方设备互操作。
5 changes: 4 additions & 1 deletion docs/HSMS-SESSION-STATE-MACHINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
- 本地和远端 Separate 都终止当前协议会话;
- 只有 Selected 状态的数据消息才向上游事件流转发;
- 本地数据发送同样进入 actor 做 Selected 与当前 Session ID 校验,任务
在 <code>IHsmsTransport</code> 确认完整写出后完成。
在 <code>IHsmsTransport</code> 确认完整写出后完成;
- 可显式启用独立控制消息元数据观测:入站在协议处理前记录,出站在完整
写出确认后记录,默认关闭且不影响协议事件。

连接主动/被动模式不表示 Host/Equipment 业务角色。状态机没有
Host/Equipment 分支,后续角色 API 可以独立组合。
Expand Down Expand Up @@ -70,6 +72,7 @@ System Bytes。旧会话的帧、发送完成和计时器回调不会推进替
- Selected 前拒绝数据、Selected 后转发数据;
- Linktest/Deselect 的写出确认、T6、响应匹配、拒绝和并发互斥;
- Reject 黄金向量、四类生成路径、未消费 Reject 转发;
- 完整 Select/Linktest 收发观测顺序和未知 SType 到 Reject 的头字段保留;
- 非法控制头、本地及远端 Separate;
- Selected 数据发送门控、完整写出确认、会话关闭和替换会话不重放。

Expand Down
8 changes: 5 additions & 3 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,14 @@ E30-0526 材料。下一可执行优先级已转入阶段 3;SML 调试读写
- [x] 显式启用、可缩放且单次等待封顶的 Trace 重放时序。
- [x] 不包含异常与原始帧的受限结构化诊断 Trace 导出和严格读取。
- [x] 公共未认领控制消息的受限头元数据 Trace 导出和严格读取。
- 完整控制面观测和故障原始样本等互操作诊断扩展。
- [x] 默认关闭、不含 Body/原始字节的完整控制面头元数据观测。
- 故障原始样本等互操作诊断扩展。

当前 Trace 基线不接管连接事件流,也不把原始标识写回线上。时序重放默认
关闭,并继续经过显式 allowlist 与公共发送 API。诊断快照始终排除
<code>Error</code> 与 <code>Frame</code>。控制元数据只覆盖应用事件循环已经
取得的未认领控制帧,不声称观察内部握手。后续完整控制观测与故障原始样本
<code>Error</code> 与 <code>Frame</code>。完整控制元数据观测必须在连接
选项中显式启用,覆盖内部握手但只复制方向、actor 状态和十字节头,不含
Body、原始线上字节、transport generation 或时间戳。后续故障原始样本
扩展必须先定义原始字节的数据分级、默认不导出策略和可验证脱敏边界。
E173 SMN 与 E172 SEDD 仍需先核对授权标准及允许分发的 Schema/表示形式。

Expand Down
35 changes: 22 additions & 13 deletions docs/TRACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
- 只选择显式允许的本地发送记录,并通过正常公共发送 API 串行重放;
- 可选按缩放且封顶的源时间间隔执行受控时序重放;
- 不包含异常和原始帧的结构化 HSMS 诊断快照导出与严格读取;
- 公共未认领控制事件的十字节头元数据导出与严格读取。
- 显式启用的完整控制观测或公共未认领控制事件的十字节头元数据导出与
严格读取。

当前切片不是原始网络抓包器,不记录 TCP 字节、transport Session generation、
控制帧的线上原始字节或异常对象,也不会接管 <code>HsmsConnection</code> 的
Expand Down Expand Up @@ -90,17 +91,18 @@ Session ID、System Bytes、远端状态字节和被拒绝的 SType。枚举只
与 <code>SecsFrame-Trace/1</code> 消息信封相互独立,不能交给重放器。协议
标识和状态字节仍是运维元数据;存储和共享文件时仍需执行访问控制与保留策略。

## 未认领控制消息元数据
## 控制消息元数据

应用在自己的连接事件循环中可以对
<code>ControlMessageReceived</code> 创建只包含头字段的快照:
推荐在连接选项中显式启用完整控制观测,并由应用持续消费独立流后创建只
包含头字段的快照:

~~~csharp
if (connectionEvent.Kind == HsmsConnectionEventKind.ControlMessageReceived)
await foreach (var observation in connection
.GetControlMessageObservationsAsync(cancellationToken))
{
var record = SecsTraceControlRecord.CreateReceived(
var record = SecsTraceControlRecord.Create(
DateTimeOffset.UtcNow,
connectionEvent);
observation);
var text = new SecsTraceControlCodec().Encode(new[] { record });
}
~~~
Expand All @@ -117,10 +119,17 @@ Session ID、Header Byte 2、Header Byte 3、PType、SType 和 System Bytes。
字节字段使用固定宽度大写十六进制。SType 以原始字节保存:<code>0x00</code>
因表示 Data Message 而拒绝,未知非零值允许严格往返,便于保留互操作证据。

控制帧按核心模型不能携带 Body,快照类型也没有 Body 字段。当前公共事件只
转发未被 Select、Linktest、Deselect 或数据事务认领的控制帧,主要用于未匹配
Reject;这个工具不会订阅内部状态机,也不是完整控制面抓包。控制信封不能
进入消息重放器,头字段仍应按运维元数据执行访问控制与保留策略。
控制帧按核心模型不能携带 Body,快照类型也没有 Body 字段。完整观测流默认
关闭,启用后覆盖内部 Select、Linktest、Deselect、Reject 与 Separate:
入站记录发生在协议处理前,出站只记录成功写出的帧。Trace 工具不会自行
订阅该单消费者流,也不提供原始线上字节或 transport Session generation。
极快响应与本地发送完成回调可能以不同顺序进入 actor,因此记录顺序不是
PCAP 级线上时序;需要线速证据时不能从本元数据流推断字节先后。

既有 <code>SecsTraceControlRecord.CreateReceived</code> 仍可从业务事件流的
<code>ControlMessageReceived</code> 创建快照,但该事件只转发未被会话或
数据事务认领的控制帧,主要用于未匹配 Reject。两条创建路径生成同一信封;
控制信封不能进入消息重放器,头字段仍应按运维元数据执行访问控制与保留策略。

## 结构化脱敏

Expand Down Expand Up @@ -213,5 +222,5 @@ var results = await new SecsTraceReplayer().ReplayWithTimingAsync(
测试还验证重新分配 Session ID/System Bytes 并获得新 Secondary。它证明
当前工具组合边界,不代表设备命令安全性或 SEMI 一致性认证。诊断信封另有
黄金向量、可选字段往返、严格枚举/十六进制、资源限制及异常/帧内容不泄漏
测试。控制信封另有未匹配 Reject 黄金向量、未知 SType 往返、Data Message
拒绝、严格字段与资源限制测试。
测试。控制信封另有完整 Select/Linktest 双向观测、未匹配 Reject 黄金向量、
未知 SType 往返、Data Message 拒绝、严格字段与资源限制测试。
30 changes: 30 additions & 0 deletions src/SecsFrame.Trace/SecsTraceControlRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,36 @@ public static SecsTraceControlRecord Create(
header.SystemBytes);
}

/// <summary>Creates a metadata record from a full control-message observation.</summary>
public static SecsTraceControlRecord Create(
DateTimeOffset timestamp,
HsmsControlMessageObservation observation)
{
if (observation is null)
throw new ArgumentNullException(nameof(observation));

var direction = observation.Direction switch
{
HsmsControlMessageDirection.Sent => SecsTraceDirection.Sent,
HsmsControlMessageDirection.Received => SecsTraceDirection.Received,
_ => throw new ArgumentOutOfRangeException(
nameof(observation),
observation.Direction,
"Unknown control-message direction."),
};
var header = observation.Header;
return new SecsTraceControlRecord(
timestamp,
direction,
observation.State,
header.SessionId,
header.HeaderByte2,
header.HeaderByte3,
header.PresentationType,
(byte)header.MessageType,
header.SystemBytes);
}

/// <summary>
/// Creates a received metadata record from an unclaimed public control event.
/// </summary>
Expand Down
Loading
Loading