Darra_Profinet_Slave 全栈跨层契约交叉审计报告
审计轮: 2026-08-14 (全栈审计 wave · 跨层契约交叉审计 agent)
范围: Darra_Profinet_Slave 全仓 (排除 bin/obj/out/_archive/_Stage/其他参考/ThirdParty 内容本体; ThirdParty 仅作语义对照取证)
性质: 只读审计, 不修代码。本文只落档证据与分级, 修复由其他 agent 按文件域执行。
六轴审计: ① 端口/端点 ② 配置 schema 端到端 ③ 命名 ④ 0x/十进制口径 ⑤ 文档一致性 ⑥ 版本。
0. 结论速览
| 级别 | 数量 | 主题 |
|---|
| P0 跨层语义反转 | 1 | 输入/输出方向语义在 GSDML/控制面(标准) 与 驱动/服务数据面(反转) 之间交叉 — 见 §2.1 |
| P1 文档与代码冲突/内部矛盾 | 3 | 十六进制规范 vs 用户新裁定 0x; rt-class-1.md 两种方向口径并存; 共存契约 BindForm 过时 |
| P2 口径错位 | 4 | IOPS/IOCS 每子槽语义; 报警严重度枚举两套; INF 版本治理; quickstart 示例路径 |
| P3 信息/登记 | 2 | "IO" 第三方向不存在于配置链; 设计文档版本单源与 BindForm 表述 |
| PASS 轴 | — | 端口 18840 单源 / 0x-十进制生成器→验证器→C 解析器 / JSON camelCase / 版本 1.0.0 全链 (细节见各节) |
1. 端口/端点轴
| 检查项 | 各层证据 | 结论 |
|---|
| 服务 HTTP API 端口 18840 单源 | 服务常量 PnetApiServer.FixedPort = 18840 (PnetApiServer.cs:111); 客户端常量 GuiClient.FixedPort = 18840 + DefaultBaseUrl = http://127.0.0.1:18840 (GuiClient.cs:56/59); GUI 常量 PnetApiPort = 18840 (MainForm.cs:531); appsettings.json 注释声明"不提供 Api:Port 键"; docs/docs/index.md / 设计文档.md §3 一致 | ✅ PASS, 全仓单源一致, 仅本地回环 |
| 18821 (DarraLink) 是否误用 | 全仓 18821 仅 2 处命中, 均为注释"仿 DarraLink 18821 固定端口原则" (PnetApiServer.cs:7/110, SDK DarraPnet.cs:339), 无任何代码使用 | ✅ PASS, 与 DarraLink 无耦合 |
驱动设备路径 \\.\DarraRT_Pnet | appsettings.json:38 DriverDevicePath = "\\\\.\\DarraRT_Pnet"; PnetConfigService.cs:60 默认值同; PnetDriverBridge.cs:355 DevicePath = @"\\.\DarraRT_Pnet"; 设计文档.md:230 一致 | ✅ PASS, 三处同值 |
命名 Section \Device\DarraRT_Pnet_GlobalIO | PnetDriverBridge.cs:358 SectionName; PnetRuntime.cs:26; 设计文档.md:252 一致 | ✅ PASS |
| 其余端口变体 (1884x/1882x/1844x) | 全仓 grep 无其他固定端口 | ✅ PASS |
2. 配置 schema 端到端轴
链路: PnetProject (GUI 模型) → device-config.dpnet (JSON 持久化) → ConfigWriter 运行配置 XML → 服务 PnetConfigService.Load → BuildIocrConfigs (IOCR) → /api/io 元数据 → GUI 数据测试显示。
2.1 ★ P0 — 输入/输出方向语义跨层反转 (GSDML/控制面 vs 驱动/服务数据面)
同一份 XML 的 InputLength 在控制面与数据面被映射到相反的协议方向:
| 层 | Input 的语义 | 证据 |
|---|
| GUI 模型枚举注释 | 输入 = 从站 → PLC (标准) | GsdmlWriter.cs:38-43 PnetIoDirection.Input, Output 注释 "输入 (从站 -> PLC) / 输出 (PLC -> 从站)" |
| GSDML 输出 | Direction==Input → <Input> 元素 (GSDML 规范: 从站 → 控制器) | GsdmlWriter.cs:576-600 AddDirection(PnetIoDirection.Input, "Input") |
| 运行配置 XML | IoItem Direction="Input" / InputLength = Σ Input 条目 | ConfigWriter.cs:333 / 311-317 |
| 文档口径 | concepts.md:21-26 "Input: 从站 → 控制器"; :90-91 "PPM(Provider)=从站→控制器 输入区数据; CPM(Consumer)=控制器→从站 输出区数据"; rt-class-1.md:45 "PPM 发输入数据, CPM 收输出数据" | 标准语义 |
| 上游 p-net (ThirdParty) | PNET_DIR_INPUT 子模块 → PF_DIRECTION_INPUT + IOPS (Provider=发送方); C 核心 pnet_wrapper.c:186-210 由 input_len>0 推 PNET_DIR_INPUT | 标准语义, 控制面据此插子模块 |
| 服务 BuildIocrConfigs (数据面) | ΣInputLength → IOCR[0] = CPM (0x0200, 控制器→设备, 写"输入区"); ΣOutputLength → IOCR[1] = PPM (0x0100, 设备→控制器, 读"输出区") | PnetRuntime.cs:2112-2128 + 2181-2221 |
| 驱动 | CPM (控制器→设备) 收帧写入共享内存"输入区"; PPM (设备→控制器) 从"输出区"组帧发送 | DarraRT_Pnet_Iocr.c:4-7 |
| /api/io | InputHex = "输入区数据 (应用从 PDI 收, 控制器 → 设备)"; OutputHex = "设备 → 控制器" | PnetApiModels.cs:56-57/68-69 |
| GUI 数据测试 | "输入区: inputHex (控制器 → 设备, CPM); 输出区: outputHex (设备 → 控制器, PPM)" | DataTestView.cs:8-10 |
| SDK 服务模式 | 补偿映射注释: "SDK I 区 → 服务端 OutputArea (PPM 设备→控制器); SDK Q/DB 区 → 服务端 InputArea (CPM 控制器→设备)" | SDK DarraPnet.cs:1382-1387 |
判定: 控制面 (上游栈 + GSDML + 文档) 全部采用标准语义 (Input=从站→控制器=PPM); 数据面 (服务 IOCR 构建 + 驱动 + /api/io + 数据测试 UI) 命名反转 (CPM 区叫"输入区")。后果:
- GSDML
<Input> 声明的大小 = Σ Input 条目, 但运行时设备实际发送 (PPM) 的大小 = Σ OutputLength。真实控制器 (TIA) 按 GSDML 期望收 N 发 M, 设备侧 CPM 窗口 = N、PPM 发送 = M — 输入/输出 CR 尺寸交叉。Input≠Output 的不对称工程在参数化/CConnect 阶段必然失配; 对称工程 (N==M) 侥幸不暴露。
- 驱动 CPM/PPM 帧方向本身正确 (0x0100=PPM/0x0200=CPM 各层一致), 错的是区名与 InputLength/OutputLength 的挂接方向。
- 服务内部用 p-net 的 output API 处理"输入区" (PnetRuntime.cs:1820-1822
pnet_output_set_iocs 语义), 用 input API 处理"输出区" (PnetRuntime.cs:1927-1929), 是桥接补偿 — 说明两层语义冲突已被意识到但未在配置链源头归一。
修复方向建议 (供修复 agent): 以标准语义为准 (GUI 模型注释 / GSDML / 上游栈 / concepts.md 四方一致), 改 BuildIocrConfigs 的挂接 (ΣInputLength → PPM 0x0100 发送区, ΣOutputLength → CPM 0x0200 接收区) 并同步驱动共享内存区名与 /api/io、DataTestView、SDK 注释。涉及文件域: PnetRuntime.cs / DarraRT_Pnet_{Iocr,Shared,Frame}.c/h / PnetApiModels.cs / DataTestView.cs / SDK DarraPnet.cs — 与 r2-input-consistency / q1-device-input-official-mask agent 的文件域可能重叠, 派修前先对文件域分配表。
2.2 各跳字段核对 (方向问题除外, 均 PASS)
| 跳 | 字段 | 证据 | 结论 |
|---|
| 模型 → XML | StationName/DeviceName/VendorID(0x%04X)/DeviceID(0x%04X)/HardwareRelease/SoftwareRelease | ConfigWriter.cs:275-285; 往返校验 VerifyRoundTrip 逐字段比对 (87-250) | ✅ |
| 模型 → XML | ModuleIdentNumber/SubmoduleIdentNumber 属性 0x 固定 8 位; Slot/Subslot 属性十进制 | ConfigWriter.cs:313-324 | ✅ |
| 模型 → XML | IoItem Direction="Input"/"Output"、Offset/Length 十进制属性 + Name 元素 | ConfigWriter.cs:331-337 | ✅ (Direction 语义见 §2.1) |
| XML → 服务 Load | ParseHex16 支持 0x 前缀; SendClock 毫秒 → CycleMs (钳 1..1000, 三层统一 audit-2026-08-13); <DeviceName> → ProductName | PnetConfigService.cs:451-493 | ✅ |
| 配置分层 | 优先级 环境变量 DARRA_PNET_* > XML > appsettings; 6 键 PnetEnvKeys 单一权威; DriverAdapterName 最终化 = NetworkInterfaceName (单一网口收敛) | PnetConfigService.cs:168-330 | ✅ |
| XML → 服务过程映像 | 过程映像区大小由 SlotLayout 子模块 InputLength/OutputLength 累加推导 (与 BuildIocrConfigs 同规则), 弃 256/256 静态默认 | PnetConfigService.cs:505-551 (audit-2026-08-14) | ✅ (方向挂接见 §2.1) |
| XML 缺失 | 服务拒绝启动 (如实抛异常), 不静默空跑 | PnetConfigService.cs:255-270 | ✅ |
| XML 路径 | 相对路径按 AppContext.BaseDirectory 解析 + 落 Info 日志; GUI 下发落盘契约 C:\DARRA\Profinet_Slave\config\runtime-config.xml (MainForm.cs:1416-1444 双候选: GUI 目录 ../config 优先) | PnetConfigService.cs:236-253; MainForm.cs | ✅ |
| IOCR 构建 | CPM/PPM DataLength = Σ 子模块长度; 超 1440 (PnetDriverAbi.MaxIoData) 如实报错; 周期 µs = CycleMs×1000 钳制 | PnetRuntime.cs:2112-2227 | ✅ (方向见 §2.1) |
| /api/io 元数据 | InputLength/OutputLength (字节) + hex 大写文本 + FormatVersion=1 + IoEpoch | PnetApiModels.cs:50-94; PnetApiServer.cs:586-596 Convert.ToHexString (大写) | ✅ |
| /api/io → GUI 数据测试 | 区大小权威 = 元数据 InputLength/OutputLength, hex 解析长度仅兜底; 16 字节/行网格; KB=字节/1024 | DataTestView.cs:527-556 | ✅ |
| JSON 序列化契约 | 服务端 camelCase (JsonOptions, PnetApiServer.cs:1458-1460) ↔ SDK 最小解析器按 camelCase 键消费 (DarraPnet.cs:1930-1960) | ✅ | |
| 枚举方向 "IO" | 全配置链 (GUI 模型/XML/C 解析器 pnet_config.c:1208-1221) 仅 Input/Output; "IO" 仅存在于 SDK 派生 (InOut, 双长度>0, DarraPnet.cs:1319-1321) 与原生常量 (PNET_DIR_IO / PNET_DIR_NO_IO, PnetNative.cs:91-99) | ✅ 一致, 登记 | |
| C 解析器 Direction | 仅 "Input"(0)/"Output"(1), 其他字符串报错 "IoItem.Direction 非法" | pnet_config.c:1208-1221 | ✅ |
3. 命名轴 (三层命名)
| 层 | 名 | 用法 | 结论 |
|---|
| 驱动组件/服务名 (netcfg/sc) | DarraRT_Pnet | INF AddService = DarraRT_Pnet (:39); install-pnet.ps1 $ComponentName='DarraRT_Pnet' (:42); GUI 注册表探测 SYSTEM\CurrentControlSet\Services\DarraRT_Pnet (MainForm.cs:528, SplashScreen.cs:206) | ✅ 正确 (服务键 = 组件名) |
| NDIS 协议注册名 (BindForm) | DarraRTPnet (去下划线) | DARRT_PNET_NDIS_PROTOCOL_NAME = L"DarraRTPnet" (DarraRT_Pnet_Internal.h:62); Nic.c:636-644 注释: netcfg 按组件 ID 剥下划线生成 BindForm, NDIS Linkage 写在 Services\DarraRTPnet 下 | ✅ 正确 (roundPNETCOMPAT2), 但文档未跟上 (§5) |
| HW ID / cat | darrart_pnet (小写) | INF :9/:23-26 | ✅ |
| Windows 服务 | DarraPnetService | ServiceInstaller.cs:55/61 | ✅ |
| 原生 DLL | darra_pnet.dll (小写) | CMake OUTPUT_NAME "darra_pnet" (Darra_Pnet_Core/CMakeLists.txt:72-73); PnetNative.cs:36 DllPath = "darra_pnet.dll" (2026-08-12 大小写修复, .197 大小写敏感卷); PnetRuntime.cs:1587 按 darra_pnet*.dll 探测 | ✅ 一致 (文档/docs 用展示名 DarraPnet.dll 属别名) |
| 设备对象 | \\.\DarraRT_Pnet / \Device\DarraRT_Pnet_GlobalIO | §1 | ✅ |
4. 0x/十进制口径轴
| 环节 | 口径 | 证据 | 结论 |
|---|
| GUI 输入归一 | 纯数字按十六进制解读 (1 → 0x0001), 0x/0X 同义, 失焦归一 0x 小写前缀 + 大写 + 固定 4 位 | DeviceView.cs:350-384 | ✅ |
| 模型 | ushort/uint 原生值 | GsdmlWriter.cs:163-169 | ✅ |
| GSDML 生成 (表 A) | VendorID/DeviceID 0x%04X; ModuleIdentNumber/SubmoduleIdentNumber 0x%08X | GsdmlWriter.cs:335-336/407/538/556/671-673 | ✅ |
| GSDML 生成 (表 B) | SubslotNumber "0x8000"/"0x8001"; FixedInSubslots 0x{subslot:X} — 0x (注释: 本仓用户裁定统一 0x 输出); FixedInSlots "0" 十进制 (XSD 拒绝 0x) | GsdmlWriter.cs:413-414/437-448/481-501/542 | ✅ (与验证器一致, 但与规范文档冲突 → §5 P1-2) |
| GSDML 生成 (表 C) | PhysicalSlots "0..N" 十进制; MinDeviceInterval/SendClock = ms×32 (31.25µs 时基, 下限 32); MaxInputLength/OutputLength 十进制 | GsdmlWriter.cs:406/452-453/493/679-683 | ✅ |
| 验证器 | 表 A 四标识号强制 0x; 表 B 槽号类双格式 (十进制或 0x, 值域 ≤65535) 均不判错; 表 C 十进制 | ValidationService.cs:748-820 | ✅ 与生成器输出自洽 |
| 核心 GSDML 解析器 | 只解析 4 标识号 (严格 0x) | GSDML_十六进制属性规范.md §8 (pnet_gsdml.c) | ✅ |
| 核心测试 | 期望 SubslotNumber="0x8000" / FixedInSubslots="0x1" / MinDeviceInterval="32" (V4 已修) | test_pnet_gsdml.c:66/83-96 | ✅ 与生成器一致 |
| 运行配置轨 | VendorID/DeviceID 0x%04X 元素; 标识号 0x%08X 属性; 长度/偏移/槽号十进制; 服务 ParseHex16/ParseHex32 解析; TEST_Config _三端闭环断言 0x 前缀 | ConfigWriter.cs:284-285/313-335; PnetConfigService.cs:566-582; _三端闭环.cs:287-297 | ✅ |
| 端到端判定 | 生成器 → 验证器 → 解析器 → 测试期望 无转换回退 | 本节全表 | ✅ PASS (仅规范文档口径滞后, §5) |
5. 文档一致性轴
| # | 级别 | 文档 | 过时/冲突点 | 代码现状 |
|---|
| D1 | P1 | docs/GSDML_十六进制属性规范.md | §0/§3/§8 钉死"表 B (SubslotNumber/FixedInSubslots) 项目统一口径 = 十进制" (对齐西门子导出); §7 V1/V2 描述的也是"生成器输出十进制、校验器误判"旧状态 | 代码 (GsdmlWriter.cs:444-449/540-542) 已按用户新裁定统一 0x 输出 ("本仓用户裁定统一 0x 输出, 保留现状"), 核心测试 test_pnet_gsdml.c 期望 0x。规范文档与新裁定相反 — 后续 agent 按文档改码会把 0x 回退成十进制 |
| D2 | P1 | docs/docs/protocol/rt-class-1.md | L41-42 (0x0100=PPM/0x0200=CPM, 与驱动一致) vs L45 "PPM(Provider)发输入数据,CPM(Consumer)收输出数据" + L56-57 "PPM: 输入数据/CPM: 输出数据" (标准语义, 与驱动区名相反) — 同一文档两套方向口径并存 | 驱动: CPM→"输入区", PPM→"输出区" (§2.1)。文档内部 L45/56-57 与 L73-77 亦自相矛盾 |
| D3 | P1 | docs/PNET_ETH_共存契约.md | ① §2.1 K1c: Services\DarraRT_Pnet\Linkage "NDIS 绑定时自动派生" — roundPNETCOMPAT2 后 NDIS 按注册名写 Services\DarraRTPnet\Linkage (Internal.h:56-57); ② §2.3: DeviceView.cs:437 读 Class\{4D36E972...}\Linkage\UpperBind 判绑定 — 该查询已按 2026-08-14 用户裁定移除 (DeviceView.cs:22-26 "选中即绑定, OS 绑定状态查询已移除"); ③ §4 token 表无 DarraRTPnet 分类 | 文档需补 BindForm 键域 + 更新 §2.3 |
| D4 | P2 | docs/docs/guide/quickstart.md:98 | 示例路径 C:\Darra\Profinet\device.xml 与产品实际契约不符 | 实际: GUI 建议名 pnet-<站点名>-config.xml, 部署契约 C:\DARRA\Profinet_Slave\config\runtime-config.xml |
| D5 | P2 | 设计文档.md:9 | "版本号单源 version/ 目录" | sync-versions.ps1 头注释: "单源 = CMakeLists.txt 的 project(DarraProfinet VERSION)" (version/*.txt 是被同步产物) — 两文档互斥 |
| D6 | P3 | 设计文档.md:230 | "NDIS 协议驱动 DarraRT_Pnet" 未区分组件名 vs BindForm 注册名 DarraRTPnet | 表述不精确, 易引导后续代码把注册名写回带下划线 (1168 根因复发) |
| D7 | P3 | docs/GSDML_TIA_导入合规报告.md §5 | R2 (writer 错误注释)/R3 (PNIO_Version V2.4)/R5 (站点名校验缺口)/R6 (缺 CertificationInfo) 已全部在代码中修复 (V2.32/ValidateAndNormalizeStationName/CertificationInfo 均现存在); R4 VendorID 0x0001 默认测试值仍属实 | 报告为历史快照, 各 R 项状态需标注"已修"避免重复派修 |
6. 版本轴
| 源 | 值 | 证据 | 结论 |
|---|
| CMakeLists.txt (声明单源) | project(DarraProfinet VERSION 1.0.0) | 根 CMakeLists.txt:3 | ✅ |
| version/core.txt / driver.txt / sdk.txt | 1.0.0 ×3 | version/ 目录 | ✅ 与 CMake 同步 |
| GUI | VersionText = "v1.0.0" (MainForm.cs:76); Designer 默认 Caption "v1.0.0" (:584); csproj Version 1.0.0; 在线时显示服务 Version 补 v 前缀 | MainForm.cs:1018-1026 | ✅ |
| 服务 | ProductVersion = "1.0.0" (PnetApiServer.cs:128), csproj Version 1.0.0 | | ✅ |
| SDK | CSharp csproj Version 1.0.0 | | ✅ |
| INF DriverVer | 08/12/2026,1.0.0.3 | DarraRT_Pnet.inf:10 | ⚠ P2: INF 走独立版本轨 (每次 sys 字节变升号, roundPNETCOMPAT2 升 1.0.0.3, 注释有完整理由), 但 version/driver.txt=1.0.0 不反映 INF 版本, sync-versions.ps1 自称"单源"却不覆盖 INF — 版本治理口径漂移, 建议在 sync-versions.ps1 或 INF 注释互指 |
7. 附: 其余跨层观察 (登记, 不构成断链)
- IOPS/IOCS 粒度 (P2):
PnetProcessImage 按 _inputSize/_outputSize (数据字节数) 分配 IOPS/IOCS 缓冲 (PnetProcessImage.cs:160-164), 运行时整区 GOOD/BAD 填充 (PnetRuntime.cs:225-228); 而 IoSnapshot 注释与 PROFINET 语义为"每子槽 1 字节"。当前子模块级状态被整区状态取代 — 服务无每子槽粒度 (上游 API 本身是每子槽 1 字节, pnet_wrapper.c:246-248)。多子槽工程时 IOPS 区长度 (数据长) ≠ 子槽数, 与标准口径错位。
- 报警严重度枚举 (P2): GUI 模型
PnetAlarmSeverity = Info/Warning/Critical (3 值, 2026-08-09 裁定严重度不再编辑, 保留兼容) vs 服务 PnetAlarmSeverity = Info/Warning/Error/Critical (4 值); PnetApiModels.cs:102 注释称 "PnetAlarmSeverity 口径" 跨层同名不同值。API 消费者 (GUI 报警展示) 遇 "Error" 需确认本地化覆盖。
- 自动选卡废止 (P3): PnetConfig.cs 注释"自动选卡已移除 (2026-08-14 用户裁定)", "自动"/AUTO/* 由 ValidateConfiguredNic 启动期报错 — 与 DeviceView"选中即绑定"一致; 需确认共存契约/设计文档无"自动选卡"残留承诺 (grep 见 PnetConfigService.cs:21-26, 文档未再提)。
- FrameId 硬编码: BuildIocrConfigs 固定 0x0100/0x0200, 注释如实标注"真实 AR 建立后控制器协商的 FrameId 可能不同" (PnetRuntime.cs:2077-2081) — 已诚实标注, 登记。
本报告由跨层契约交叉审计 agent 只读落档 (2026-08-14)。P0 §2.1 方向反转是全栈最优先修复项, 派修前须与 r2-input-consistency / q1-device-input-official-mask 等活跃 agent 做文件域隔离 (涉及 PnetRuntime.cs、驱动 Iocr/Frame/Shared、PnetApiModels.cs、DataTestView.cs、SDK DarraPnet.cs、rt-class-1.md、concepts.md)。