wireless-rb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +21 -0
  3. data/LICENSE +22 -0
  4. data/README-zh.md +103 -0
  5. data/README.md +103 -0
  6. data/docs/architecture-zh.md +92 -0
  7. data/docs/release.md +13 -0
  8. data/examples/offline.rb +29 -0
  9. data/lib/wireless/ap_session.rb +119 -0
  10. data/lib/wireless/collector.rb +168 -0
  11. data/lib/wireless/configuration.rb +22 -0
  12. data/lib/wireless/coverage.rb +60 -0
  13. data/lib/wireless/fields.rb +131 -0
  14. data/lib/wireless/h3c/authentication_parser.rb +25 -0
  15. data/lib/wireless/h3c/command_output.rb +29 -0
  16. data/lib/wireless/h3c/configuration.rb +109 -0
  17. data/lib/wireless/h3c/parser.rb +13 -0
  18. data/lib/wireless/h3c/parsers/display_current_configuration.rb +246 -0
  19. data/lib/wireless/h3c/parsers/display_dot1x_connection.rb +13 -0
  20. data/lib/wireless/h3c/parsers/display_lldp_neighbor_information_verbose.rb +83 -0
  21. data/lib/wireless/h3c/parsers/display_mac_authentication_connection.rb +13 -0
  22. data/lib/wireless/h3c/parsers/display_wlan_ap_all_verbose.rb +80 -0
  23. data/lib/wireless/h3c/parsers/display_wlan_ap_model_all.rb +79 -0
  24. data/lib/wireless/h3c/parsers/display_wlan_bss_all_verbose.rb +41 -0
  25. data/lib/wireless/h3c/parsers/display_wlan_client_security_block_mac.rb +31 -0
  26. data/lib/wireless/h3c/parsers/display_wlan_client_verbose.rb +49 -0
  27. data/lib/wireless/h3c/parsers/display_wlan_statistics_client_ip_conflict.rb +50 -0
  28. data/lib/wireless/h3c/profile.rb +45 -0
  29. data/lib/wireless/h3c/session_parser.rb +58 -0
  30. data/lib/wireless/h3c/verbose_parser.rb +82 -0
  31. data/lib/wireless/h3c_runtime.rb +18 -0
  32. data/lib/wireless/huawei_runtime.rb +65 -0
  33. data/lib/wireless/neighbor_parser.rb +25 -0
  34. data/lib/wireless/observation.rb +28 -0
  35. data/lib/wireless/parser_registry.rb +9 -0
  36. data/lib/wireless/projection.rb +16 -0
  37. data/lib/wireless/runtime_parser.rb +47 -0
  38. data/lib/wireless/snapshot.rb +37 -0
  39. data/lib/wireless/version.rb +6 -0
  40. data/lib/wireless.rb +18 -0
  41. metadata +154 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e202198d7c272a5d1463d5bfd91fb56057d440783d3e11f2316187673d296be3
4
+ data.tar.gz: 2ce8943f91535c5950cc3ae0bec031edf5a5564debbfaaae1770938b2ebf9962
5
+ SHA512:
6
+ metadata.gz: d4eacfd2e94f6228d10e230f4ad2b44bf103c835cc89463cbee4734db266589f8767f4a01cbafddf39b510c51e7b84cd841b5ea26818ba39278372630b42e4fe
7
+ data.tar.gz: 7bb9649c07bb3b7d580ba47779ac10183a5df6d112500391db9853749b8a156b24ccce8e422836d4b677e0f85e70356b6908afdc02289f66c8f9d75d734f33ce
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ Changes to the public contract are recorded here. Versions follow Semantic Versioning.
4
+
5
+ ## 0.1.0 (2026-10-16)
6
+
7
+ ### Added
8
+
9
+ - Controller configuration/runtime parsers, shared AP/radio views, and host-independent observations.
10
+ - Standalone RubyGems packaging under `wireless-rb` with the `wireless` require path and `Wireless` namespace.
11
+ - English and Chinese documentation, an offline example, and standalone RSpec/RuboCop development tasks.
12
+ - Linux/macOS CI, isolated package verification, and automatic RubyGems and GitHub Releases with identical artifacts.
13
+
14
+ ### Fixed
15
+
16
+ - Keep MAC authentication passwords out of normalized results and require successful projection status for complete coverage.
17
+
18
+ ### Changed
19
+
20
+ - Cross-module dependencies resolve through RubyGems/Bundler instead of relative sibling paths.
21
+ - Motor persistence and application-specific adapters remain in the host application.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Song Huang
4
+ Copyright (c) 2026 Gatework contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README-zh.md ADDED
@@ -0,0 +1,103 @@
1
+ # wireless-rb
2
+
3
+ [English](README.md) | [源码](https://github.com/gatework/wireless-rb) | [变更记录](CHANGELOG.md)
4
+
5
+ 以纯 Ruby 解析无线控制器配置和运行观察。厂商命令解析器建模 AP、Radio、客户端和服务配置,采集事实与库存持久化分开。
6
+
7
+ ## 能力
8
+
9
+ - H3C 配置、AP、Radio、BSS、客户端、AP 组、服务模板及认证观察。
10
+ - Huawei 已支持的 AP/客户端运行格式;未支持维度明确标识。
11
+ - 同一命令共享解析,每个结果视图独立保留完整性。
12
+ - 通过内置 net-connector AP 会话适配器可选采集 AP 邻居。
13
+
14
+ ## 安装
15
+
16
+ 要求 **Ruby 4.0.6 或更新版本**。在应用的 `Gemfile` 中添加:
17
+
18
+ ```ruby
19
+ gem "wireless-rb", require: "wireless"
20
+ ```
21
+
22
+ 然后执行 `bundle install`;也可直接执行 `gem install wireless-rb`。Ruby 加载入口为 `wireless`,公开命名空间为 `Wireless`;gem 包名与 Ruby 命名空间分别维护。
23
+
24
+ 模块运行依赖:[`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver), [`net-connector`](https://github.com/gatework/net-connector)。 gemspec 的运行依赖不添加版本约束;应用应提交自己的 `Gemfile.lock`,保持部署可复现。使用本库不需要启动 Rails 或配置兄弟模块的本地路径。
25
+
26
+ 发布产物和校验值见 [GitHub Releases](https://github.com/gatework/wireless-rb/releases),已发布版本以 [RubyGems](https://rubygems.org/gems/wireless-rb) 为准。
27
+
28
+ ## 离线示例
29
+
30
+ 在开发检出目录安装依赖后执行:
31
+
32
+ ```sh
33
+ bundle exec ruby examples/offline.rb
34
+ ```
35
+
36
+ 示例使用合成数据,不访问网络。完整代码见 [examples/offline.rb](examples/offline.rb):
37
+
38
+ ```ruby
39
+ require "wireless"
40
+
41
+ # 使用合成 H3C AP 回显;配置上限不代表实时功率测量。
42
+ output = <<~CLI
43
+ Total number of APs : 1
44
+ AP name : AP-EXAMPLE
45
+ AP ID : 7
46
+ MAC address : 0200-0000-0007
47
+ IP address : 192.0.2.7
48
+ State : Run
49
+ Radio 1 :
50
+ Admin state : Up
51
+ Radio type : 802.11ax 6GHz
52
+ Channel : 1(auto)
53
+ Channel bandwidth : 80MHz
54
+ Operating bandwidth : 40MHz
55
+ Channel usage(%) : 0
56
+ Noise floor : -90 dBm
57
+ Max power : 20
58
+ CLI
59
+ observations = Wireless::RuntimeParser.new.parse_many(
60
+ %i[access_points radios], output, platform: "h3c", complete: true
61
+ )
62
+ observations.each do |name, observation|
63
+ puts "#{name}: #{observation.status}, records=#{observation.records.size}"
64
+ end
65
+ # 两个视图共用一次解析,各自保留完整性状态。
66
+ ```
67
+
68
+ ## 公开契约与限制
69
+
70
+ `Configuration.new.parse(text, complete: false)` 解析控制器配置。`RuntimeParser.new(model: nil).parse(name, text, platform: "h3c", complete: false)` 解析单个运行维度;`parse_many` 为同一命令的多个维度共享解析。`NeighborParser` 为邻居观察补充 AP 上下文。
71
+
72
+ 输入完整性由调用方确认。未知语法、身份重复、数量矛盾和截断输出保持部分或失败。配置中的最大发射功率不能当作当前测量功率;PSK、密码、认证 URL 不可进入快照。
73
+
74
+ `Collector#collect(connector:, target:, platform:, model: nil, dimensions:, ap_connection: nil, ap_names: [])` 使用注入的 connector。内置 `ApSession` 通过显式运行依赖 `net-connector` 处理中转会话;请求 LLDP 时必须通过 `ap_connection` 显式提供 AP 凭据。宿主负责设备身份和连接设置,解析器不会推断 AP console 开启或 AP 凭据。Rails 库存投影、锁、加密、API 和 `Wireless::Sync` 属于宿主,不随本 gem 发布。
75
+
76
+ 详细厂商语义、历史验证记录和 Motor 专用集成说明保留在[架构参考](docs/architecture-zh.md)。其中宿主集成代码需要 Motor,不属于独立 gem API。适配层位于 [Motor 仓库](https://github.com/gatework/motor/tree/main/backend/lib) 的 `backend/lib/baseline`、`backend/lib/vxlan` 和 `backend/lib/wireless`。
77
+
78
+ 生命周期 connector 是宿主适配器,提供 `open { |session| }`,VXLAN 和 Wireless 还要求 `platform`;原生 `Net::Connector::Base` 实例并不提供这一宿主生命周期协议。已连接的原生会话可传入 `CtxDriver::Reader` 或 `CtxChecker::Collector#collect_session`,不会隐式连接或重试。
79
+
80
+ ## 开发与测试
81
+
82
+ ```sh
83
+ bundle install
84
+ bundle exec rake
85
+ bundle exec ruby examples/offline.rb
86
+ bundle exec rake build
87
+ ```
88
+
89
+ `bundle exec rake` 执行 RSpec、RuboCop 及离线示例。测试放在 `spec/`,沿用项目 RSpec 约定;样例采用合成或脱敏证据。新增解析格式时补充成功、不完整、不支持及非法输入回归。离线测试不代表所有设备版本均通过实机验证。
90
+
91
+ 使用声明的最低 Ruby 版本检查兼容性。方法用途统一使用简明中文注释;复杂实现还需说明业务规则、实现思路及关键分支。保持公开返回值与错误契约。真实回显和凭据不可进入示例、fixture、日志或发布包。
92
+
93
+ ## 发布
94
+
95
+ 本目录可独立维护为 [wireless-rb](https://github.com/gatework/wireless-rb) 仓库。更新 `VERSION` 和 `CHANGELOG.md`,运行上述验证与构建命令,检查包内文件,再创建 `vVERSION` 标签。
96
+
97
+ CI 在 Linux、macOS 上执行测试、lint、示例、构建和隔离安装。`main` 的 CI 通过后推送 `vVERSION` 标签,工作流将已验证的 Linux 产物发布到 RubyGems 和 GitHub Release,附带 `SHA256SUMS` 并回读下载字节。GitHub `release` environment 可配置 `RUBYGEMS_API_KEY` secret;未配置时使用 RubyGems Trusted Publishing,绑定本仓库、`release.yml` 和 `release` environment。失败时重跑失败的发布 job,以复用原 CI 产物;同版本已存在时必须校验字节一致。详见[发布说明](docs/release.md)。
98
+
99
+ 先发布依赖模块。标签、构建成功或工作流文件都不等于已经发布,需单独确认 RubyGems 的实际结果。版本变化见 [CHANGELOG.md](CHANGELOG.md)。
100
+
101
+ ## 许可证
102
+
103
+ MIT,见 [LICENSE](LICENSE)。
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # wireless-rb
2
+
3
+ [简体中文](README-zh.md) | [Source](https://github.com/gatework/wireless-rb) | [Changelog](CHANGELOG.md)
4
+
5
+ Wireless controller configuration and runtime observations in pure Ruby. Vendor command parsers model access points, radios, clients, and service configuration while keeping observations separate from inventory persistence.
6
+
7
+ ## Features
8
+
9
+ - H3C configuration, access points, radios, BSSs, clients, groups, service templates, and authentication observations.
10
+ - Limited Huawei AP/client runtime formats; unsupported dimensions remain explicit.
11
+ - Shared command parsing with independent completeness for each resulting view.
12
+ - Optional AP neighbor collection through the included net-connector AP-session adapter.
13
+
14
+ ## Installation
15
+
16
+ Requires **Ruby 4.0.6 or newer**. Add this line to your application's `Gemfile`:
17
+
18
+ ```ruby
19
+ gem "wireless-rb", require: "wireless"
20
+ ```
21
+
22
+ Then run `bundle install`, or install directly with `gem install wireless-rb`. The require path is `wireless` and the public namespace is `Wireless`; package names and Ruby namespaces are intentionally separate.
23
+
24
+ Internal runtime dependencies: [`ctx_parser`](https://github.com/gatework/ctx_parser), [`ctx_driver`](https://github.com/gatework/ctx_driver), [`net-connector`](https://github.com/gatework/net-connector). The gemspec declares runtime dependencies without version constraints. Applications should commit their own `Gemfile.lock` to make deployments reproducible. No Rails boot or sibling checkout paths are required.
25
+
26
+ Release artifacts and checksums are available from [GitHub Releases](https://github.com/gatework/wireless-rb/releases). Check [RubyGems](https://rubygems.org/gems/wireless-rb) for published versions.
27
+
28
+ ## Offline example
29
+
30
+ From a development checkout after installing dependencies:
31
+
32
+ ```sh
33
+ bundle exec ruby examples/offline.rb
34
+ ```
35
+
36
+ The example uses synthetic data and performs no network access:
37
+
38
+ ```ruby
39
+ require "wireless"
40
+
41
+ # 使用合成 H3C AP 回显;配置上限不代表实时功率测量。
42
+ output = <<~CLI
43
+ Total number of APs : 1
44
+ AP name : AP-EXAMPLE
45
+ AP ID : 7
46
+ MAC address : 0200-0000-0007
47
+ IP address : 192.0.2.7
48
+ State : Run
49
+ Radio 1 :
50
+ Admin state : Up
51
+ Radio type : 802.11ax 6GHz
52
+ Channel : 1(auto)
53
+ Channel bandwidth : 80MHz
54
+ Operating bandwidth : 40MHz
55
+ Channel usage(%) : 0
56
+ Noise floor : -90 dBm
57
+ Max power : 20
58
+ CLI
59
+ observations = Wireless::RuntimeParser.new.parse_many(
60
+ %i[access_points radios], output, platform: "h3c", complete: true
61
+ )
62
+ observations.each do |name, observation|
63
+ puts "#{name}: #{observation.status}, records=#{observation.records.size}"
64
+ end
65
+ # 两个视图共用一次解析,各自保留完整性状态。
66
+ ```
67
+
68
+ ## Public contract and limits
69
+
70
+ `Configuration.new.parse(text, complete: false)` parses controller configuration. `RuntimeParser.new(model: nil).parse(name, text, platform: "h3c", complete: false)` parses one runtime dimension; `parse_many` shares parsing for dimensions from the same command. `NeighborParser` supplies the AP context to neighbor observations.
71
+
72
+ The caller must establish input completeness. Unknown syntax, duplicate identities, count mismatches, and truncated output remain partial or failed. A configured maximum transmit power is not a current measured transmit power. PSKs, passwords, and authentication URLs must not enter snapshots.
73
+
74
+ `Collector#collect(connector:, target:, platform:, model: nil, dimensions:, ap_connection: nil, ap_names: [])` uses an injected connector. `net-connector` is an explicit runtime dependency for the included `ApSession`; an LLDP request needs explicit AP credentials through `ap_connection`. The host owns device identity and connection settings. AP console activation and AP credentials are never inferred by the parser. Rails inventory projection, locking, encryption, APIs, and `Wireless::Sync` belong to the host and are not shipped in this gem.
75
+
76
+ Detailed vendor behavior, historical validation notes, and Motor-only integration are preserved in [the architecture reference](docs/architecture-zh.md). Host integration examples in that reference require Motor; they are not standalone gem APIs. Motor adapters live under `backend/lib/baseline`, `backend/lib/vxlan`, and `backend/lib/wireless` in the [Motor repository](https://github.com/gatework/motor/tree/main/backend/lib).
77
+
78
+ The lifecycle connector is a host adapter exposing `open { |session| }` (and `platform` for VXLAN and Wireless). It is distinct from a native `Net::Connector::Base` device instance. Borrowed native sessions are accepted by `CtxDriver::Reader` and `CtxChecker::Collector#collect_session`; they are never implicitly connected or retried.
79
+
80
+ ## Development and tests
81
+
82
+ ```sh
83
+ bundle install
84
+ bundle exec rake
85
+ bundle exec ruby examples/offline.rb
86
+ bundle exec rake build
87
+ ```
88
+
89
+ `bundle exec rake` runs RSpec, RuboCop, and the offline examples. Tests live in `spec/`, following the existing project's RSpec convention; fixtures use synthetic or sanitized evidence. Add regression cases for successful, incomplete, unsupported, and malformed inputs when extending a parser. Offline tests do not certify all device versions or replace live integration checks.
90
+
91
+ Use the declared minimum Ruby version for compatibility checks. Document source methods and non-obvious business logic in concise Chinese, preserve public return/error contracts, and never add raw device captures or credentials to examples, fixtures, logs, or release archives.
92
+
93
+ ## Release
94
+
95
+ This directory can be maintained as the independent [wireless-rb](https://github.com/gatework/wireless-rb) repository. Update `VERSION` and `CHANGELOG.md`, run the validation and build commands above, and inspect the packaged file list before tagging `vVERSION`.
96
+
97
+ CI runs tests, lint, examples, build, and an isolated installation on Linux and macOS. After CI passes on `main`, push `vVERSION` to run the release workflow. It publishes the tested Linux artifact to RubyGems and GitHub Releases, attaches `SHA256SUMS`, and verifies downloaded bytes. The GitHub `release` environment accepts a `RUBYGEMS_API_KEY` secret; without it, configure RubyGems Trusted Publishing for this repository, `release.yml`, and the `release` environment. Retry a failed publish job to reuse its CI artifact; an already published version must have identical bytes. See [release details](docs/release.md).
98
+
99
+ Publish dependency gems first. A Git tag, a successful build, or a workflow file does not establish that the gem has reached RubyGems; confirm the registry result separately. See [CHANGELOG.md](CHANGELOG.md) for release notes.
100
+
101
+ ## License
102
+
103
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,92 @@
1
+ > 本文保留模块从 Motor 提取前的领域设计、覆盖范围、限制与验证记录,供兼容迁移和故障分析使用。
2
+ > 当前独立 gem 的安装、依赖、开发与发布入口以 [README-zh.md](../README-zh.md) 为准。
3
+ > 涉及 `Motor::*`、`Sync`、`Baseline::Check`、数据库、Rails、Rake 运维任务及私有实机证据的内容均属于 Motor 宿主集成,不随 gem 提供;历史验证记录不代表当前用户设备已经验证。
4
+
5
+ # 无线设备采集与入库
6
+
7
+ Motor 自有的无线业务实现,参考 Netstack 已验证的命令、字段及完整性规则,按现有 Rails 模型重新组织。数据模型继续使用 `Motor::WirelessAp` 等现有类,不新增表或字段。
8
+
9
+ ## 实现方案
10
+
11
+ `Motor::Device → Motor::Devices::Connector → Wireless::Collector / CtxDriver::Reader → Configuration / RuntimeParser / NeighborParser → Snapshot → Sync → AssetInventory / DetailInventory`
12
+
13
+ - 公共 `Connector` 管理凭据、严格主机密钥校验和会话生命周期;[`CtxDriver::Reader`](https://github.com/gatework/ctx_driver) 通过 net-connector 执行单条查询、验证响应并记录来源摘要。每台控制器使用一个会话顺序采集,共享命令只执行、解析一次;单脚本失败不丢弃其他结果,断线后不自动重连。AP 子会话仍由 `ApSession` 管理,每个 AP 的邻居读取和解析分别隔离失败。
14
+ - 解析结果使用与数据库字段对应的 Hash。H3C 覆盖 AP、Radio、BSS、Client、AP 组、服务模板、802.1X,以及快照中的 MAC 认证和阻断客户端。Huawei 保留已验证的 AP、Client 表格格式,其他维度明确标记不支持。
15
+ - 配置事实和运行测量分开保存;配置中的最大功率不能当作当前发射功率。PSK、密码、原始配置及认证 URL 不进入快照或错误信息。
16
+ - AP LLDP 需要显式请求,使用控制器中转和显式 AP 凭据。确认 AP 提示符后才读取,退出时确认原控制器提示符;不自动开启 AP console。
17
+ - 外部 I/O 完成后锁控制器,复核控制器、原快照和资产版本,原子提交资产、配置明细、快照及 revision。采集事实与库存投影分开:缺父 AP/Radio 的合法记录保留在原始快照,库存投影记录 `parent_unavailable`,其他独立实体继续入库;存在依赖未满足的维度禁止清理缺失项。身份歧义、模型校验和数据库异常仍使整个事务回滚。AP 档案只标记缺失,人工管理字段保留。
18
+ - 管理 API、Netdisco 同步和既有 Rake 任务名不变。`Motor::Wireless::CliSync` 委托新实现。虚拟控制器及信道继续查询现有表。
19
+
20
+
21
+ H3C 具体策略位于 [`lib/wireless/h3c`](../lib/wireless/h3c/profile.rb),10 条命令分别实现为 `parsers/display_*.rb`。`H3c::Parser` 管理平台输入边界,`VerboseParser` 与 `SessionParser` 复用标签和会话结构,各命令类维护自己的字段和身份规则。`Profile` 统一声明命令、解析类及型号覆盖,`ParserRegistry` 识别 `h3c` / `h3c_wireless`。公共抽象及型号扩展见 [`../ctx_parser`](https://github.com/gatework/ctx_parser)。
22
+
23
+ 配置与运行命令共用 `H3c::CommandOutput` 的编码和命令错误边界;配置保留原始换行,运行表格规范化为 LF。`DisplayCurrentConfiguration` 解释语法,`H3c::Configuration` 维护实体身份、AP 组/型号/Radio 的合法归属及模板认证类型。嵌套记录与来源路径由 `Configuration::Scope` 一起构建,逐行状态和捕获复用 `CtxParser::ConfigurationParser`,不在厂商类内重复实现。
24
+
25
+ 顶层声明只在配置根节点识别。AP、型号或 Radio 内出现 `sysname`、`wlan ap-group`、`wlan service-template` 等顶层声明时,保留未知诊断,不提升为全局对象;配置及共享的组/模板维度均为部分结果,入库保留已有完整明细。重复实体保留首条并标记为部分结果。
26
+
27
+ AP/Radio 共用 `display wlan ap all verbose` 的一次解析,分别保存完整性;配置/AP组/模板同样共享一次配置解析。LLDP 命令类只接受回显,外部 AP 名称由 `NeighborParser` 注入。采集器把控制器型号或 AP 自身型号传入对应策略。
28
+
29
+ ## 验证范围
30
+
31
+ 离线 fixture 验证字段、语法覆盖、重复身份、截断、显式空清单及秘密过滤;替身会话验证登录策略、命令顺序和断线;真实 PostgreSQL 验证关联、部分更新、重复投递、删除、并发冲突及回滚。这些验证与真实设备联调分别报告。
32
+
33
+ ## 调用与兼容
34
+
35
+ ```ruby
36
+ result = ::Wireless::Sync.new.call(controller: Motor::Device.find(device_id))
37
+ result.counts
38
+ result.observation.complete? # 原始采集完整性
39
+ result.complete? # 采集与库存投影都完成
40
+ result.projection.dimensions # 各库存维度的 applied/deferred 和安全诊断
41
+
42
+ # 显式采集 AP 邻居;使用已保存的加密 AP 凭据。
43
+ ::Wireless::Sync.new.call(controller: device, dimensions: [:lldp])
44
+
45
+ # 筛选 AP 时该维度保持 partial,避免删除未采集 AP 的邻居。
46
+ ::Wireless::Sync.new.call(controller: device, dimensions: [:lldp], ap_names: ["AP-LAB-01"],
47
+ ap_connection: { password: ap_password })
48
+
49
+ # 离线解析不建立会话、不写数据库。
50
+ ::Wireless::Configuration.new.parse(configuration_text, complete: true)
51
+ ::Wireless::RuntimeParser.new.parse(:clients, client_output, platform: "h3c", complete: true)
52
+ ```
53
+
54
+ 固定 AP 密码保存在现有 `Motor::EncryptedConfig` 的内部键 `network.wireless_ap`,值为 `password` 和可选 `username`。现有 Active Record Encryption 加密该值,配置中心通用接口不公开此内部键;不新增字段或第二套密钥存储。凭据仅在显式 LLDP 请求时读取,调用参数优先,不进入观察记录、默认检查输出和日志。
55
+
56
+ 保留 `Motor::Wireless::CliSync` 和 `motor:netstack:wireless[device_id]` 运维入口。内部 Ruby 调用从 `Motor::Netstack::Wireless::Sync` 改为 `::Wireless::Sync`;返回值包含 `observation/counts/revision/projection`,不再返回 Netstack 的 `reconciliation/assessment`。Rake 同时报告采集和投影状态;依赖未满足时保存可用事实后以非零状态退出。其他业务的 Netstack 依赖保留。
57
+
58
+ 新观察按维度名称保存 `name/status/records/issues/evidence/started_at/completed_at`。历史快照无需回填;首次新采集按维度更新,失败时原 `last_complete` 仍保留旧 JSON 形状。未请求维度保持原样,不能把旧完整数据当作本次成功。
59
+
60
+ 库存投影结果保存在现有 `controller.motor_settings["wireless_cli"]["projection"]`,无需新表。每维度记录采样时间、观察/应用/延后数量、未解析父关联总数和最多 20 条身份诊断;原始采集维度仍保留自身的完整性。后续成功投影会替换旧的阻塞诊断,未请求维度不变。
61
+
62
+ 控制器、AP 和 Radio API 增加 `coverage`,分别显示最新采集状态、采样时间、最后完整时间和投影状态。最新失败、部分、未请求或过期证据不能支持 `healthy`;独立已知故障仍报告,并同时展示覆盖不足。控制器逐实体计算新鲜度,一个新 AP 不会使旧 AP 恢复为在线。没有 CLI 快照的既有 Netdisco 库存保持旧健康视图,并显式标记 `source=inventory/state=untracked`,不宣称 CLI 完整覆盖。旧快照没有投影元数据时兼容原子入库成功的历史行为;已有投影条目与最新采集时间不匹配则报告 `projection_time_unconfirmed`。
63
+
64
+ AP 列表按当前页批量聚合 Radio/BSS/Client 数量、SSID 客户分布及 Radio 利用率,查询数不随页内 AP 数量线性增加。Radio 列表和 AP 详情共用控制器证据,不按 Radio 重复读取快照。其他清单与同步全库存的锁定策略保持不变。
65
+
66
+ 当前限制:筛选 AP 的 LLDP 结果仍以整体 `partial` 保存,不能表达“选定 AP 范围内完整”,因此不会删除该范围的旧邻居;这避免影响未采集 AP,但定向刷新后的陈旧链接仍需后续范围模型解决。CLI 与 Netdisco 尚未建立跨来源优先级,`coverage` 明确表示 CLI 证据,不能将两个来源的时间混为一份实时快照。
67
+
68
+ Rails 自动加载本目录,开发容器更新后需重启 Rails/Sidekiq 以加载新路径。现有 API、权限、后台 i18n 和表结构不变。
69
+
70
+ ```sh
71
+ bundle exec rspec spec/vendor/wireless spec/services/motor/wireless spec/requests/wireless_spec.rb \
72
+ spec/lib/motor/netstack/collection_spec.rb spec/lib/motor/netstack/concurrency_spec.rb
73
+ bundle exec rails zeitwerk:check
74
+ bundle exec rubocop
75
+ ```
76
+
77
+ ## IP 冲突与 AP 型号能力
78
+
79
+ 默认 H3C 采集包含 `display wlan statistics client-ip-conflict`(`client_ip_conflicts`)和
80
+ `display wlan ap-model all`(`ap_models`)。也可通过 `dimensions: %i[client_ip_conflicts ap_models]` 单独采集。
81
+ 解析器按设备接受的完整命令名注册;`dis` 是设备 CLI 的缩写。
82
+
83
+ 两类记录通过 `Wireless::Sync` 原子保存至现有 `Motor::WirelessSnapshot.observations` JSONB,
84
+ 路径分别是 `client_ip_conflicts.latest.records` 和 `ap_models.latest.records`,无需 migration。
85
+ 失败或部分结果保留 `last_complete`;未请求的维度不变。型号能力不创建 AP 资产或运行 Radio。
86
+ 冲突记录包含 IPv4/IPv6、新旧 MAC/AP ID 和 `occurred_at`(设备原始 `MM-DD HH:MM:SS`,不推断年份或时区)。
87
+ 型号记录包含厂商、许可、Radio 能力和硬件版本对应的软件/镜像信息。
88
+ 完整传输的冲突表头且无数据行表示空表;空回显、未知行、非法字段和重复身份不会作为完整结果。
89
+
90
+ 格式依据 H3C 官方 [IP snooping 命令参考](https://www.h3c.com/en/d_202603/2774466_294551_0.htm)
91
+ 及 [AP management 命令参考](https://www.h3c.com/en/d_202509/2658869_294551_0.htm)。
92
+ fixtures 为依据文档构造的离线数据,不是现场设备采集。
data/docs/release.md ADDED
@@ -0,0 +1,13 @@
1
+ # Release
2
+
3
+ 1. Update `lib/wireless/version.rb` and `CHANGELOG.md`; run `bundle exec rake` and `bundle exec rake build`.
4
+ 2. Run `ruby script/verify_package.rb pkg/wireless-rb-VERSION.gem`. This installs and loads the built gem outside Bundler and the checkout.
5
+ 3. Commit and push `main`; wait for both Linux and macOS CI jobs.
6
+ 4. Push the matching `vVERSION` tag. The Release workflow repeats CI, then publishes its exact Linux artifact to RubyGems and GitHub with `SHA256SUMS`.
7
+ 5. Verify the workflow, registry version, tag commit, and downloaded checksums. A local build is not publication evidence.
8
+
9
+ Configure `RUBYGEMS_API_KEY` as an encrypted secret in the GitHub `release` environment, with noninteractive gem-push permission. Alternatively configure [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) for `gatework/wireless-rb`, `release.yml`, environment `release`, and omit that secret. Never commit credentials.
10
+
11
+ A failed publish is not permission to overwrite an existing version. Rerun the failed publish job to retain the original CI artifact. The script verifies an existing RubyGems version before proceeding and rejects different bytes or yanked versions. `workflow_dispatch` is restricted to version tags by the publish validation.
12
+
13
+ Raw device `*.log`, local secrets, and build outputs are ignored. Package files use an explicit gemspec allowlist; review both the Git index and gem contents before publishing. Tests and examples must use synthetic or sanitized evidence.
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "wireless"
4
+
5
+ # 使用合成 H3C AP 回显;配置上限不代表实时功率测量。
6
+ output = <<~CLI
7
+ Total number of APs : 1
8
+ AP name : AP-EXAMPLE
9
+ AP ID : 7
10
+ MAC address : 0200-0000-0007
11
+ IP address : 192.0.2.7
12
+ State : Run
13
+ Radio 1 :
14
+ Admin state : Up
15
+ Radio type : 802.11ax 6GHz
16
+ Channel : 1(auto)
17
+ Channel bandwidth : 80MHz
18
+ Operating bandwidth : 40MHz
19
+ Channel usage(%) : 0
20
+ Noise floor : -90 dBm
21
+ Max power : 20
22
+ CLI
23
+ observations = Wireless::RuntimeParser.new.parse_many(
24
+ %i[access_points radios], output, platform: "h3c", complete: true
25
+ )
26
+ observations.each do |name, observation|
27
+ puts "#{name}: #{observation.status}, records=#{observation.records.size}"
28
+ end
29
+ # 两个视图共用一次解析,各自保留完整性状态。
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/connector"
4
+ require "ctx_driver"
5
+ require_relative "fields"
6
+
7
+ module Wireless
8
+ # Wireless 负责 AP 接入业务;net-connector 负责交互、超时、敏感输出与互斥。
9
+ class ApSession
10
+ DEVICE_PROMPT = /(?:\A|(?<=[\r\n]))[ \t\x00]*<[^<>\r\n]+>[ \t\x00]*[\r\n]*\z/
11
+ CONNECTION_FAILURE = /Connection\s+(?:refused|timed\s+out|closed|reset|was\s+closed)|No\s+route\s+to\s+host|Unable\s+to\s+connect/i
12
+ AUTHENTICATION_FAILURE = /Login\s+incorrect|Authentication\s+failed/i
13
+ COMMAND = "display lldp neighbor-information verbose".freeze
14
+ class Error < StandardError
15
+ attr_reader :code
16
+
17
+ # 保留稳定 AP 失败代码,不包含凭据或设备正文。
18
+ def initialize(code)
19
+ @code = code
20
+ super("AP session failed")
21
+ end
22
+ end
23
+
24
+ # 校验 AP 凭据,借用控制器会话进行嵌套接入。
25
+ def initialize(controller:, password:, username: nil)
26
+ unless password.is_a?(String) && !password.strip.empty? && !/[\x00-\x1f\x7f]/.match?(password) &&
27
+ (username.nil? || (username.is_a?(String) && !/[\x00-\x1f\x7f]/.match?(username)))
28
+ raise ArgumentError, "Invalid AP credentials"
29
+ end
30
+ @controller, @password, @username = controller, password, username
31
+ @usable = true
32
+ end
33
+
34
+ # 嵌套登录 AP 读取 LLDP,并确认返回原控制器提示符。
35
+ def read(access_point:)
36
+ return { error: :controller_session_lost, skipped: true } unless @usable && @controller.connected?
37
+ return { error: :ap_not_online, skipped: true } unless Fields.online_status(access_point[:operational_status]) == "online"
38
+ address = Fields.ip(access_point[:management_ip])
39
+ return { error: :ap_address_unavailable, skipped: true } unless address && IPAddr.new(address).ipv4? && address != "0.0.0.0"
40
+
41
+ outcome = {}
42
+ @controller.with_operation(:wireless_ap_lldp) do
43
+ original_prompt = prompt
44
+ raise Error, :controller_prompt_mismatch unless DEVICE_PROMPT.match?(original_prompt)
45
+ entered = false
46
+ returned = false
47
+ begin
48
+ interactions = [ Net::Connector::Interaction.new(/password:\s*\z/i, "#{@password}\n", sensitive: true, limit: 1, capture: false) ]
49
+ interactions.unshift(Net::Connector::Interaction.new(/(?:login|username):\s*\z/i, "#{@username}\n", sensitive: true, limit: 1, capture: false)) if @username
50
+ login = execute("telnet #{address}", DEVICE_PROMPT, timeout: 10, interactions: interactions)
51
+ confirm(login)
52
+ returned = prompt == original_prompt
53
+ # 只有提示符已切换到 AP 才确认登录,命令成功返回控制器不代表接入成功。
54
+ entered = prompt != original_prompt && DEVICE_PROMPT.match?(prompt)
55
+ raise Error, :ap_authentication_failed if AUTHENTICATION_FAILURE.match?(login.output)
56
+ raise Error, :ap_connection_failed if CONNECTION_FAILURE.match?(login.output)
57
+ raise Error, :ap_login_not_confirmed unless entered
58
+ reader = CtxDriver::Reader.new(session: @controller)
59
+ preparation = reader.read("screen-length disable")
60
+ raise Error, preparation.failure.fetch(:code) unless preparation.success?
61
+
62
+ result = reader.read(COMMAND)
63
+ outcome[:result] = result
64
+ raise Error, result.failure.fetch(:code) unless result.success?
65
+ rescue Error, Net::Connector::Error => error
66
+ outcome[:error] = error.is_a?(Error) ? error.code : Net::Connector::ErrorMetadata.code(error.code) || :collection_failed
67
+ ensure
68
+ begin
69
+ # 已进入 AP 才发送退出;必须确认返回原提示符后才能继续复用控制器会话。
70
+ if entered && @controller.connected?
71
+ confirm(execute("quit", exact_prompt(original_prompt)))
72
+ returned = prompt == original_prompt
73
+ end
74
+ @usable = returned && @controller.connected? && prompt == original_prompt
75
+ rescue Error, Net::Connector::Error
76
+ @usable = false
77
+ end
78
+ outcome[:cleanup_error] = :ap_exit_failed unless @usable
79
+ end
80
+ end
81
+ outcome
82
+ rescue ArgumentError, IPAddr::InvalidAddressError
83
+ { error: :ap_address_unavailable, skipped: true }
84
+ rescue Error, Net::Connector::Error => error
85
+ @usable = false
86
+ { error: error.is_a?(Error) ? error.code : Net::Connector::ErrorMetadata.code(error.code) || :collection_failed }
87
+ ensure
88
+ begin
89
+ @controller.close if !@usable && @controller.connected?
90
+ rescue Net::Connector::Error
91
+ outcome[:cleanup_error] = :session_close_failed if outcome
92
+ end
93
+ end
94
+
95
+ # 展示对象身份与状态,避免泄露原始回显或凭据。
96
+ def inspect = "#<#{self.class.name} usable=#{@usable}>"
97
+
98
+ private
99
+
100
+ # 渲染当前终端提示符,用于安全核对嵌套会话身份。
101
+ def prompt = Net::Connector::TerminalRenderer.render(@controller.current_prompt.to_s).strip
102
+
103
+ # 只有匹配原控制器提示符,才确认 AP 会话清理完成。
104
+ def exact_prompt(value) = /(?:\A|(?<=[\r\n]))[ \t\x00]*#{Regexp.escape(value)}[ \t\x00]*[\r\n]*\z/
105
+
106
+ # 通过连接器操作边界执行敏感 AP 交互。
107
+ def execute(text, prompt, timeout: 30, interactions: [])
108
+ command = Net::Connector::Command.new(text, prompt: prompt, timeout: timeout, interactions: interactions,
109
+ sensitive: true, output_sensitive: true)
110
+ @controller.execute_operation(Net::Connector::Script.new([ command ]), name: :wireless_ap_lldp, privilege: false)
111
+ end
112
+
113
+ # 要求恰好一条交互命令成功完成。
114
+ def confirm(result)
115
+ raise result.error, cause: nil unless result.success?
116
+ raise Error, :ap_response_missing unless result.steps.one?
117
+ end
118
+ end
119
+ end