diff --git a/CHANGELOG.md b/CHANGELOG.md index cea3b3b..9a71f01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,11 @@ ### 新增 +- 公共 HsmsConnectionOptions 增加工程参考计时器值 + ReferenceT3–ReferenceT8 与 + CreateWithReferenceTimers 便捷工厂;参考值与 MIT 许可的 + Secs4Net 实现及通行 HSMS-SS 部署实践交叉参照,未经授权 E37/E37.1 核对, + 不构成标准默认值声明,显式构造路径保持不变。 - 增加本机 SecsFrame.CommunicationDemo 通讯测试工具,提供 Active/Passive 显式连接配置、真实 TCP 回环、严格 SML 编辑与样例、 数据/控制消息操作,以及最多 500 条的会话内活动和结构化诊断视图。 diff --git a/docs/HSMS-CONNECTION.md b/docs/HSMS-CONNECTION.md index 8dae7ad..9e241f6 100644 --- a/docs/HSMS-CONNECTION.md +++ b/docs/HSMS-CONNECTION.md @@ -45,6 +45,89 @@ Host/Equipment 是后续业务能力角色,不由 Active/Passive 推断。 整毫秒范围;Passive 监听重试不被解释成 T5。每个取值应依据团队合法获得 的 E37/E37.1 版本、设备通信手册和互操作证据确定。 +## 应用配置绑定示例 + +库本身不依赖任何配置框架,也不读取应用配置文件;下面的绑定代码属于 +宿主应用,展示在 ASP.NET Core 或通用主机中从 appsettings.json +构造连接选项的一种推荐方式。 + +~~~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" + } +} +~~~ + +计时键使用标准 TimeSpan 文本格式,配置绑定器可以直接转换。 +把计时键声明为可空并从配置中省略,即可回退到工程参考值;参考值的来源 +与非标准核对口径见 +[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() + ?? throw new InvalidOperationException( + "The 'Hsms' configuration section is missing."); + + return settings.ToConnectionOptions(); +}); +~~~ + +HsmsConnectionOptions 构造函数继续承担全部边界校验:端口 +区间、控制 Session ID、T5/T8 的整毫秒与范围约束在绑定时立即抛出参数 +异常,宿主应用应让它在启动期中断而不是捕获吞掉。该示例只组装启动 +参数,不改变连接生命周期——修改配置后需要构造新的选项和连接,已建立 +的连接不支持运行中调整计时器。 + ## 生命周期 ~~~csharp diff --git a/docs/STREAMFRAME-ADAPTER.md b/docs/STREAMFRAME-ADAPTER.md index 6a18d07..eaa782e 100644 --- a/docs/STREAMFRAME-ADAPTER.md +++ b/docs/STREAMFRAME-ADAPTER.md @@ -86,7 +86,11 @@ StreamFrame 的 SessionExpiredException 在内部边界转换为 ## T5 与 T8 内部 HsmsTransportOptions 要求调用方显式给出 T5 与 T8, -不内置未经授权标准核对的默认值。Active 连接把 T5 映射为固定的 +不内置未经授权标准核对的默认值。公共 HsmsConnectionOptions +另提供显式标注来源的 ReferenceT3–ReferenceT8 与 +CreateWithReferenceTimers 工厂;这些工程参考值与 MIT 许可的 +Secs4Net 实现及通行部署实践交叉参照,未经授权标准核对,也不改变显式 +构造路径的必填语义。Active 连接把 T5 映射为固定的 ConnectRetryDelayMs 并关闭最大退避;Passive 监听重试配置 保持调用方设置。原 StreamConnectionOptions 始终复制后再 适配,不会被原地修改。 diff --git a/src/SecsFrame/Hsms/Connection/HsmsConnectionOptions.cs b/src/SecsFrame/Hsms/Connection/HsmsConnectionOptions.cs index c550145..3b2eed7 100644 --- a/src/SecsFrame/Hsms/Connection/HsmsConnectionOptions.cs +++ b/src/SecsFrame/Hsms/Connection/HsmsConnectionOptions.cs @@ -11,6 +11,46 @@ public sealed class HsmsConnectionOptions /// The default number of retained T8 observations. public const int DefaultTransportFaultObservationCapacity = 16; + /// The engineering reference T3 reply timeout, 45 seconds. + /// + /// 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. + /// + public static readonly TimeSpan ReferenceT3 = TimeSpan.FromSeconds(45); + + /// The engineering reference T5 connect retry interval, 10 seconds. + /// + /// 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. + /// + public static readonly TimeSpan ReferenceT5 = TimeSpan.FromSeconds(10); + + /// The engineering reference T6 control reply timeout, 5 seconds. + /// + /// 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. + /// + public static readonly TimeSpan ReferenceT6 = TimeSpan.FromSeconds(5); + + /// The engineering reference T7 selection timeout, 10 seconds. + /// + /// 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. + /// + public static readonly TimeSpan ReferenceT7 = TimeSpan.FromSeconds(10); + + /// The engineering reference T8 incomplete-message timeout, 5 seconds. + /// + /// 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. + /// + public static readonly TimeSpan ReferenceT8 = TimeSpan.FromSeconds(5); + /// Creates explicit HSMS connection settings. /// The remote address in Active mode or local bind address in Passive mode. /// The TCP port, from 1 through 65535. @@ -97,6 +137,53 @@ public HsmsConnectionOptions( TransportFaultObservationCapacity = transportFaultObservationCapacity; } + /// + /// Creates connection settings that fill T3 through T8 with the + /// engineering reference values. + /// + /// The remote address in Active mode or local bind address in Passive mode. + /// The TCP port, from 1 through 65535. + /// Which peer initiates the TCP connection. + /// The protocol Session ID used by outgoing data messages. + /// + /// Enables the separate restricted-metadata control-message observation stream. + /// + /// + /// Enables the separate bounded T8 prefix-snapshot observation stream. + /// + /// + /// The maximum queued T8 observations before the oldest item is discarded. + /// + /// + /// 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. + /// + 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); + /// Gets the remote or local bind IP address. public IPAddress IpAddress { get; } diff --git a/test/SecsFrame.Tests/HsmsConnectionOptionsTests.cs b/test/SecsFrame.Tests/HsmsConnectionOptionsTests.cs index ec50948..60605c5 100644 --- a/test/SecsFrame.Tests/HsmsConnectionOptionsTests.cs +++ b/test/SecsFrame.Tests/HsmsConnectionOptionsTests.cs @@ -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(() => + HsmsConnectionOptions.CreateWithReferenceTimers( + IPAddress.Loopback, + port: 0, + HsmsConnectionMode.Active, + 10)); + } + private static HsmsConnectionOptions Create( IPAddress? ipAddress = null, int port = 5000,