Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@

### 新增

- 公共 <code>HsmsConnectionOptions</code> 增加工程参考计时器值
<code>ReferenceT3</code>–<code>ReferenceT8</code> 与
<code>CreateWithReferenceTimers</code> 便捷工厂;参考值与 MIT 许可的
Secs4Net 实现及通行 HSMS-SS 部署实践交叉参照,未经授权 E37/E37.1 核对,
不构成标准默认值声明,显式构造路径保持不变。
- 增加本机 <code>SecsFrame.CommunicationDemo</code> 通讯测试工具,提供
Active/Passive 显式连接配置、真实 TCP 回环、严格 SML 编辑与样例、
数据/控制消息操作,以及最多 500 条的会话内活动和结构化诊断视图。
Expand Down
83 changes: 83 additions & 0 deletions docs/HSMS-CONNECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,89 @@ Host/Equipment 是后续业务能力角色,不由 Active/Passive 推断。
整毫秒范围;Passive 监听重试不被解释成 T5。每个取值应依据团队合法获得
的 E37/E37.1 版本、设备通信手册和互操作证据确定。

## 应用配置绑定示例

库本身不依赖任何配置框架,也不读取应用配置文件;下面的绑定代码属于
宿主应用,展示在 ASP.NET Core 或通用主机中从 <code>appsettings.json</code>
构造连接选项的一种推荐方式。

~~~json
{
"Hsms": {
"IpAddress": "192.0.2.10",
"Port": 5000,
"ConnectionMode": "Active",
"SessionId": 0,
"T3": "00:00:45",
"T5": "00:00:10",
"T6": "00:00:05",
"T7": "00:00:10",
"T8": "00:00:05"
}
}
~~~

计时键使用标准 <code>TimeSpan</code> 文本格式,配置绑定器可以直接转换。
把计时键声明为可空并从配置中省略,即可回退到工程参考值;参考值的来源
与非标准核对口径见
[STREAMFRAME-ADAPTER.md](STREAMFRAME-ADAPTER.md)。

~~~csharp
public sealed class HsmsConnectionSettings
{
public string IpAddress { get; set; } = "";
public int Port { get; set; }
public HsmsConnectionMode ConnectionMode { get; set; }
public ushort SessionId { get; set; }
public TimeSpan? T3 { get; set; }
public TimeSpan? T5 { get; set; }
public TimeSpan? T6 { get; set; }
public TimeSpan? T7 { get; set; }
public TimeSpan? T8 { get; set; }

public HsmsConnectionOptions ToConnectionOptions()
{
if (string.IsNullOrWhiteSpace(IpAddress))
{
throw new InvalidOperationException(
"The Hsms:IpAddress configuration value is required.");
}

return new HsmsConnectionOptions(
IPAddress.Parse(IpAddress),
Port,
ConnectionMode,
SessionId,
T3 ?? HsmsConnectionOptions.ReferenceT3,
T5 ?? HsmsConnectionOptions.ReferenceT5,
T6 ?? HsmsConnectionOptions.ReferenceT6,
T7 ?? HsmsConnectionOptions.ReferenceT7,
T8 ?? HsmsConnectionOptions.ReferenceT8);
}
}
~~~

在主机启动时绑定并注册,让非法输入在启动期立即失败:

~~~csharp
builder.Services.AddSingleton(_ =>
{
var settings = builder.Configuration
.GetSection("Hsms")
.Get<HsmsConnectionSettings>()
?? throw new InvalidOperationException(
"The 'Hsms' configuration section is missing.");

return settings.ToConnectionOptions();
});
~~~

<code>HsmsConnectionOptions</code> 构造函数继续承担全部边界校验:端口
区间、控制 Session ID、T5/T8 的整毫秒与范围约束在绑定时立即抛出参数
异常,宿主应用应让它在启动期中断而不是捕获吞掉。该示例只组装启动
参数,不改变连接生命周期——修改配置后需要构造新的选项和连接,已建立
的连接不支持运行中调整计时器。

## 生命周期

~~~csharp
Expand Down
6 changes: 5 additions & 1 deletion docs/STREAMFRAME-ADAPTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ StreamFrame 的 <code>SessionExpiredException</code> 在内部边界转换为
## T5 与 T8

内部 <code>HsmsTransportOptions</code> 要求调用方显式给出 T5 与 T8,
不内置未经授权标准核对的默认值。Active 连接把 T5 映射为固定的
不内置未经授权标准核对的默认值。公共 <code>HsmsConnectionOptions</code>
另提供显式标注来源的 <code>ReferenceT3</code>–<code>ReferenceT8</code> 与
<code>CreateWithReferenceTimers</code> 工厂;这些工程参考值与 MIT 许可的
Secs4Net 实现及通行部署实践交叉参照,未经授权标准核对,也不改变显式
构造路径的必填语义。Active 连接把 T5 映射为固定的
<code>ConnectRetryDelayMs</code> 并关闭最大退避;Passive 监听重试配置
保持调用方设置。原 <code>StreamConnectionOptions</code> 始终复制后再
适配,不会被原地修改。
Expand Down
87 changes: 87 additions & 0 deletions src/SecsFrame/Hsms/Connection/HsmsConnectionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,46 @@ public sealed class HsmsConnectionOptions
/// <summary>The default number of retained T8 observations.</summary>
public const int DefaultTransportFaultObservationCapacity = 16;

/// <summary>The engineering reference T3 reply timeout, 45 seconds.</summary>
/// <remarks>
/// Cross-referenced with the MIT-licensed Secs4Net implementation and
/// common HSMS-SS deployment practice; not verified against an authorized
/// SEMI E37/E37.1 copy and not a claim of standard default values.
/// </remarks>
public static readonly TimeSpan ReferenceT3 = TimeSpan.FromSeconds(45);

/// <summary>The engineering reference T5 connect retry interval, 10 seconds.</summary>
/// <remarks>
/// Cross-referenced with the MIT-licensed Secs4Net implementation and
/// common HSMS-SS deployment practice; not verified against an authorized
/// SEMI E37/E37.1 copy and not a claim of standard default values.
/// </remarks>
public static readonly TimeSpan ReferenceT5 = TimeSpan.FromSeconds(10);

/// <summary>The engineering reference T6 control reply timeout, 5 seconds.</summary>
/// <remarks>
/// Cross-referenced with the MIT-licensed Secs4Net implementation and
/// common HSMS-SS deployment practice; not verified against an authorized
/// SEMI E37/E37.1 copy and not a claim of standard default values.
/// </remarks>
public static readonly TimeSpan ReferenceT6 = TimeSpan.FromSeconds(5);

/// <summary>The engineering reference T7 selection timeout, 10 seconds.</summary>
/// <remarks>
/// Cross-referenced with the MIT-licensed Secs4Net implementation and
/// common HSMS-SS deployment practice; not verified against an authorized
/// SEMI E37/E37.1 copy and not a claim of standard default values.
/// </remarks>
public static readonly TimeSpan ReferenceT7 = TimeSpan.FromSeconds(10);

/// <summary>The engineering reference T8 incomplete-message timeout, 5 seconds.</summary>
/// <remarks>
/// Cross-referenced with the MIT-licensed Secs4Net implementation and
/// common HSMS-SS deployment practice; not verified against an authorized
/// SEMI E37/E37.1 copy and not a claim of standard default values.
/// </remarks>
public static readonly TimeSpan ReferenceT8 = TimeSpan.FromSeconds(5);

/// <summary>Creates explicit HSMS connection settings.</summary>
/// <param name="ipAddress">The remote address in Active mode or local bind address in Passive mode.</param>
/// <param name="port">The TCP port, from 1 through 65535.</param>
Expand Down Expand Up @@ -97,6 +137,53 @@ public HsmsConnectionOptions(
TransportFaultObservationCapacity = transportFaultObservationCapacity;
}

/// <summary>
/// Creates connection settings that fill T3 through T8 with the
/// engineering reference values.
/// </summary>
/// <param name="ipAddress">The remote address in Active mode or local bind address in Passive mode.</param>
/// <param name="port">The TCP port, from 1 through 65535.</param>
/// <param name="connectionMode">Which peer initiates the TCP connection.</param>
/// <param name="sessionId">The protocol Session ID used by outgoing data messages.</param>
/// <param name="enableControlMessageObservation">
/// Enables the separate restricted-metadata control-message observation stream.
/// </param>
/// <param name="enableTransportFaultObservation">
/// Enables the separate bounded T8 prefix-snapshot observation stream.
/// </param>
/// <param name="transportFaultObservationCapacity">
/// The maximum queued T8 observations before the oldest item is discarded.
/// </param>
/// <remarks>
/// The reference timers are cross-referenced with the MIT-licensed
/// Secs4Net implementation and common HSMS-SS deployment practice. They
/// have not been verified against an authorized SEMI E37/E37.1 copy and
/// are not standard default values; callers that need audited values
/// must use the explicit constructor.
/// </remarks>
public static HsmsConnectionOptions CreateWithReferenceTimers(
IPAddress ipAddress,
int port,
HsmsConnectionMode connectionMode,
ushort sessionId,
bool enableControlMessageObservation = false,
bool enableTransportFaultObservation = false,
int transportFaultObservationCapacity =
DefaultTransportFaultObservationCapacity)
=> new(
ipAddress,
port,
connectionMode,
sessionId,
ReferenceT3,
ReferenceT5,
ReferenceT6,
ReferenceT7,
ReferenceT8,
enableControlMessageObservation,
enableTransportFaultObservation,
transportFaultObservationCapacity);

/// <summary>Gets the remote or local bind IP address.</summary>
public IPAddress IpAddress { get; }

Expand Down
73 changes: 73 additions & 0 deletions test/SecsFrame.Tests/HsmsConnectionOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,79 @@ public void T8_beyond_StreamFrame_range_is_rejected()
(long)int.MaxValue + 1)));
}

[Fact]
public void Reference_timers_match_commonly_deployed_values()
{
Assert.Equal(
TimeSpan.FromSeconds(45),
HsmsConnectionOptions.ReferenceT3);
Assert.Equal(
TimeSpan.FromSeconds(10),
HsmsConnectionOptions.ReferenceT5);
Assert.Equal(
TimeSpan.FromSeconds(5),
HsmsConnectionOptions.ReferenceT6);
Assert.Equal(
TimeSpan.FromSeconds(10),
HsmsConnectionOptions.ReferenceT7);
Assert.Equal(
TimeSpan.FromSeconds(5),
HsmsConnectionOptions.ReferenceT8);
}

[Fact]
public void Reference_timer_factory_fills_all_timers()
{
var options = HsmsConnectionOptions.CreateWithReferenceTimers(
IPAddress.Loopback,
5000,
HsmsConnectionMode.Passive,
10);

Assert.Equal(IPAddress.Loopback, options.IpAddress);
Assert.Equal(5000, options.Port);
Assert.Equal(HsmsConnectionMode.Passive, options.ConnectionMode);
Assert.Equal(10, options.SessionId);
Assert.Equal(HsmsConnectionOptions.ReferenceT3, options.T3);
Assert.Equal(HsmsConnectionOptions.ReferenceT5, options.T5);
Assert.Equal(HsmsConnectionOptions.ReferenceT6, options.T6);
Assert.Equal(HsmsConnectionOptions.ReferenceT7, options.T7);
Assert.Equal(HsmsConnectionOptions.ReferenceT8, options.T8);
Assert.False(options.EnableControlMessageObservation);
Assert.False(options.EnableTransportFaultObservation);
Assert.Equal(
HsmsConnectionOptions.DefaultTransportFaultObservationCapacity,
options.TransportFaultObservationCapacity);
}

[Fact]
public void Reference_timer_factory_passes_through_observation_settings()
{
var options = HsmsConnectionOptions.CreateWithReferenceTimers(
IPAddress.Loopback,
5000,
HsmsConnectionMode.Active,
10,
enableControlMessageObservation: true,
enableTransportFaultObservation: true,
transportFaultObservationCapacity: 7);

Assert.True(options.EnableControlMessageObservation);
Assert.True(options.EnableTransportFaultObservation);
Assert.Equal(7, options.TransportFaultObservationCapacity);
}

[Fact]
public void Reference_timer_factory_applies_the_same_input_validation()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
HsmsConnectionOptions.CreateWithReferenceTimers(
IPAddress.Loopback,
port: 0,
HsmsConnectionMode.Active,
10));
}

private static HsmsConnectionOptions Create(
IPAddress? ipAddress = null,
int port = 5000,
Expand Down
Loading