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

### 新增

- 增加独立 <code>SecsFrame.Trace</code> 程序集和版本化
<code>SecsFrame-Trace/1</code> 信封,为已解码数据消息提供确定性导出与
严格读取,并保留 UTC 时间、方向和可选 HSMS 诊断标识。
- 增加按 S/F 与 Item List 路径执行的结构化替换脱敏;路径漂移和重叠规则
明确失败,避免敏感值因字符串替换或 Schema 变化而静默泄漏。
- 增加显式 allowlist 的受控重放;只重放 Sent 记录,忽略旧 Session ID、
System Bytes、时间和回复令牌,通过公共发送 API 创建新事务。
- 增加 Trace 黄金向量、畸形信封、资源限制、脱敏泄漏、预验证和真实 TCP
新事务测试。
- 增加独立 <code>SecsFrame.Sml</code> 程序集,为动态消息和全部 Item 类型
提供确定性 SML 调试文本写出与严格读取。
- 增加 ASCII 可逆转义、JIS-8 原始十六进制字节、InvariantCulture 数字、
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
| `SecsFrame` | SECS-II 数据模型、HSMS-SS 会话与事务基础 |
| `SecsFrame.Gem` | GEM 通用状态模型与能力服务 |
| `SecsFrame.Sml` | 动态消息的确定性 SML 调试读写 |
| `SecsFrame.Trace` | 已解码消息的导出、结构化脱敏与受控重放 |
| `SecsFrame.Smn` | E173 SMN 日志、文档与消息表示 |
| `SecsFrame.Sedd` | E172 SEDD 设备接口数据字典 |

Expand All @@ -40,6 +41,9 @@ Equipment 应用发送策略的 Collection Event、报警目录查询、单报
独立 <code>SecsFrame.Sml</code> 为相同动态消息模型提供确定性文本写出、
严格读取、全部 Item 类型往返和显式资源边界;它是非规范性调试 profile,
不进入线上通讯路径。
<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 All @@ -59,6 +63,8 @@ Host/Equipment 端点见
[docs/GEM-FOUNDATION.md](docs/GEM-FOUNDATION.md),
SML 调试文本见
[docs/SML.md](docs/SML.md),
Trace 工具边界见
[docs/TRACE.md](docs/TRACE.md),
官方 secs4net 跨实现证据见
[docs/SECS4NET-INTEROP.md](docs/SECS4NET-INTEROP.md),
整体路线图见
Expand Down
1 change: 1 addition & 0 deletions SecsFrame.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<Folder Name="/src/">
<Project Path="src/SecsFrame.Gem/SecsFrame.Gem.csproj" />
<Project Path="src/SecsFrame.Sml/SecsFrame.Sml.csproj" />
<Project Path="src/SecsFrame.Trace/SecsFrame.Trace.csproj" />
<Project Path="src/SecsFrame/SecsFrame.csproj" />
</Folder>
<Folder Name="/test/">
Expand Down
4 changes: 4 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ StreamFrame TCP、Pipe、帧边界、连接生命周期
E172 SEDD 与 E173 SMN 是可选元数据/表示层,不进入核心通讯路径。
<code>SecsFrame.Sml</code> 同样是只依赖核心动态消息模型的可选调试表示层;
核心包不反向依赖它。
<code>SecsFrame.Trace</code> 依赖 SML 表示层和核心公共发送 API,只处理已经
解码的数据消息;它不订阅内部 transport 或创建第二个公共事件消费者。

## 核心原则

Expand Down Expand Up @@ -160,5 +162,7 @@ HSMS 头 Session ID 与 System Bytes。没有 W-Bit 的出站消息在写出后
- `SecsFrame` 可以依赖 `StreamFrame`。
- `SecsFrame.Gem` 依赖 `SecsFrame`。
- `SecsFrame.Sml` 依赖 `SecsFrame`,不得依赖 GEM 或线上传输实现。
- `SecsFrame.Trace` 依赖 `SecsFrame.Sml` 与 `SecsFrame`,重放必须回到
公共发送 API,不得直接构造或写出保留旧事务标识的线上帧。
- 核心包不得依赖 GEM、SML/SMN、依赖注入容器或具体日志实现。
- 状态机计时使用可替换的时间抽象,测试不得依赖真实长时间等待。
12 changes: 9 additions & 3 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,21 @@ Collection Event 发送策略沿用相同的默认不注册模式;拒绝发生
E30-0526 材料。下一可执行优先级已转入阶段 3;SML 调试读写已经基于许可
清晰的公开实现完成非规范性 profile,为现有动态 <code>SecsMessage</code>
和全部 <code>SecsItem</code> 类型提供确定性文本写出、严格读取、资源边界与
自主协议向量,不宣称标准 SML 合规。下一切片在该稳定文本表示上增加 Trace
导出、结构化脱敏与受控重放。
自主协议向量,不宣称标准 SML 合规。已在该稳定文本表示上增加已解码数据
消息的版本化 Trace 导出、结构化脱敏与显式 allowlist 受控重放;重放通过
公共发送 API 创建新事务,不复用旧会话或 System Bytes。

## 阶段 3:元数据与工具

- [x] SML 调试读写。
- E173 SMN 日志与消息文档。
- E172 SEDD 设备字典导入、校验与发现。
- Trace 重放、脱敏和互操作诊断工具。
- [x] 已解码数据消息的 Trace 导出、结构化脱敏和受控重放基线。
- 控制消息、结构化诊断、故障原始样本和重放时序等互操作诊断扩展。

当前 Trace 基线不接管连接事件流,也不把原始标识写回线上。后续控制与故障
诊断扩展必须先定义原始字节的数据分级、默认不导出策略和可验证脱敏边界。
E173 SMN 与 E172 SEDD 仍需先核对授权标准及允许分发的 Schema/表示形式。

## 发布门槛

Expand Down
114 changes: 114 additions & 0 deletions docs/TRACE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Trace 导出、脱敏与重放

## 范围

<code>SecsFrame.Trace</code> 为已经解码的 SECS-II Data Message 提供:

- 确定性的 <code>SecsFrame-Trace/1</code> 文本导出和严格读取;
- 按 Stream、Function 与 Item List 路径执行的结构化替换脱敏;
- 只选择显式允许的本地发送记录,并通过正常公共发送 API 串行重放。

当前切片不是原始网络抓包器,不记录 TCP 字节、transport Session generation、
控制帧或异常对象,也不会接管 <code>HsmsConnection</code> 的单消费者事件流。
应用应在自己的事件循环中从 <code>HsmsIncomingDataMessage</code> 创建接收记录,
并在调用发送 API 前显式创建发送记录。

## 记录与导出

<code>SecsTraceRecord</code> 保存 UTC 时间、本地方向、动态
<code>SecsMessage</code>,以及可选的协议 Session ID 和 System Bytes。
发送前通常尚不知道新事务的 System Bytes,因此这些 HSMS 字段允许缺省:

~~~csharp
using SecsFrame.Trace;

var sent = SecsTraceRecord.CreateSent(
DateTimeOffset.UtcNow,
primary,
sessionId: connection.Options.SessionId);

var received = SecsTraceRecord.CreateReceived(
DateTimeOffset.UtcNow,
connectionEvent.IncomingMessage!);

var codec = new SecsTraceCodec();
var text = codec.Encode(new[] { sent, received });
var records = codec.Decode(text);
~~~

文件信封固定使用 LF,并在每条记录前写出随后 SML 块的字符长度:

~~~text
SecsFrame-Trace/1
Record 2026-08-29T05:00:00.0000000Z Sent 10 - 10
'S1F1'W
.
~~~

字符长度使 ASCII 转义、句点和换行不会被误识别成记录边界。Trace codec
限制总字符数与记录数;每个消息继续受 <code>SmlMessageCodec</code> 的深度、
Item、值和文本长度限制。解析错误使用包含记录索引与字符偏移的
<code>SecsTraceParseException</code>。

该格式是 SecsFrame 的版本化诊断信封,不是 SEMI 标准、PCAP 或原始 HSMS
wire trace。协议标识只用于问题关联,不能作为新连接上的重放标识。

## 结构化脱敏

脱敏发生在不可变 Item 树上,而不是对 SML 文本做字符串替换:

~~~csharp
var redactor = new SecsTraceRedactor(new[]
{
new SecsTraceRedactionRule(
stream: 6,
function: 11,
itemPath: new[] { 0 },
replacement: SecsItem.Ascii("REDACTED")),
});

var safeRecord = redactor.Redact(received);
var safeText = codec.Encode(new[] { safeRecord });
~~~

空路径选择根 Item,后续整数逐层选择 List 子项。匹配消息缺少根 Item、路径
进入非 List 或索引越界时立即失败,避免消息 Schema 漂移后静默漏脱敏。
同一 S/F 上相同或祖先/后代重叠的规则在构造时拒绝;互不重叠的兄弟路径
可以一起替换。原记录和原 Item 树不会被修改。

<code>SecsTraceCodec</code> 本身不会自动脱敏。写入磁盘、日志、Issue 或测试
附件前,应用必须根据自身数据分级先执行规则,并对导出结果使用常规访问控制
和保留策略。

## 受控重放

<code>SecsTraceReplayer</code> 要求调用方提供显式 allowlist predicate:

~~~csharp
var results = await new SecsTraceReplayer().ReplayAsync(
records,
connection,
record => record.Message.Stream == 1 &&
record.Message.Function == 1,
cancellationToken);
~~~

重放具有以下固定边界:

- 先完整枚举、验证记录数量并计算 allowlist,再开始第一次发送;
- 只考虑 <code>Sent</code> 记录,<code>Received</code> 永远跳过;
- 按源顺序逐条等待正常 <code>SendAsync</code>,W-Bit 继续使用现有 T3;
- 忽略原时间、Session ID、System Bytes 和回复令牌,由活连接创建新事务;
- 不等待连接进入 Selected,不自动重试,也不按原时间间隔暂停;
- 返回每条已发送记录及新事务获得的可选 Secondary。

allowlist 是业务安全边界。库不会依据 Function 奇偶、消息名称或未知设备状态
推断 Primary/Secondary、幂等性或命令权限;应用只能允许已确认可在目标设备
与当前状态执行的消息。

## 验证边界

测试覆盖确定性信封、多记录往返、畸形头与长度、SML 嵌入错误、记录和文本
资源限制、路径漂移、规则重叠、敏感值不进入导出文本、预验证零发送,以及
真实 TCP 上重新分配 Session ID/System Bytes 并获得新 Secondary。它证明
当前工具组合边界,不代表设备命令安全性或 SEMI 一致性认证。
24 changes: 24 additions & 0 deletions src/SecsFrame.Trace/SecsFrame.Trace.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0;net10.0</TargetFrameworks>
<Description>Deterministic trace export, redaction, and controlled replay for SecsFrame.</Description>
<PackageId>SecsFrame.Trace</PackageId>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>secs;hsms;trace;redaction;replay;diagnostics;secsframe</PackageTags>
<Version>0.1.0-alpha.1</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" />
<PackageReference Include="PolySharp" PrivateAssets="all" />
<ProjectReference Include="..\SecsFrame.Sml\SecsFrame.Sml.csproj" />
<ProjectReference Include="..\SecsFrame\SecsFrame.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
Loading
Loading