跳到主要内容

全栈审计 · SDK/客户端层

审计范围: Darra_Pnet_SDK/ (C / C++ / C# / Java / Python / Rust 6 语言) + Darra_Pnet_GuiClient/ + 冒烟测试 (测试项目/TEST_SDK_*)。 审计方式: 只读审计 (未改任何代码); 契约基准 = 服务端 Darra_Pnet_Service/Api/PnetApiServer.cs + Core/PnetStateService.cs + Core/PnetNative.cs。 审计日期: 2026-08-14 (全栈审计 wave, SDK/客户端层 agent)。

0. 审计基线 (服务端契约快照)

服务端 18840 API 端点 (13 个): GET / (Web)GET /api/infoGET /api/statusGET /api/ioGET /api/alarmsGET /api/diagPOST /api/startPOST /api/stopPOST /api/ioPOST /api/config/reloadPOST /api/diagPOST /api/alarms/ackPOST /api/alarms/ack-all

近期服务端契约变更 (SDK 同步检查对象):

  1. audit-2026-08-12/13: /api/statuseverConnected 字段; 全部失败响应增 errorCode 字段; StartStopResponseErrorCode ("SDK 必须能看到服务端 errorCode — 失败响应一律携带"; "SDK 免二次轮询 /api/status")。
  2. audit-2026-08-13: StackErrErrDap = -6 (PnetRuntime.cs:2350 已用于错误文案映射)。
  3. audit-2026-08-14: PnetDeviceStateConfigError = 3 (网卡缺失优雅降级, 进程存活/控制面可用); /api/statusstatusMessage 字段 (ConfigError 态携带缺失网卡名+原因); /api/start 在 ConfigError 态返回 Ok=false + Message(含网卡名) + ErrorCode

1. 发现统计

分级数量编号
High2H1, H2
Medium5M1, M2, M3, M4, M5
Low9L1 - L9
合计16

2. High 发现

H1 — 服务端新状态 ConfigError=3 未同步到任何 SDK 状态映射; CPP 最严重 (状态查询直接抛异常)

证据:

  • 服务端 PnetStateService.cs:33 新增 ConfigError = 3 (2026-08-14), GET /api/statusstate 字符串为 "ConfigError", 且 PnetApiServer.BuildStart 明确此态为可达态 (网卡缺失时启动进入)。
  • 全 SDK 树 grep ConfigError = 0 命中 (代码与文档皆无)。
  • C# DarraPnet.cs MapServiceState (行 2358-2373): switch 仅覆盖 Stopped/Initialized/Connecting/Parameterized/Ready/DataExchange/Aborted, 无 ConfigError 分支 → 落 PnetState.Unknown
  • Java PnetService.statusEx() (行 244-262): 同样无 ConfigError → UNKNOWN
  • Rust service.rs ServiceState::from (行 176-188): 无 ConfigError → Unknown
  • C pnet_service.c (行 1025-1048): 无 ConfigError → 保持预置 PNET_DEV_STATE_UNKNOWN
  • CPP pnet.hpp StatusEx() (行 1111-1124): 未识别状态 throw PnetException("未识别的服务端状态: " + state) — ConfigError 态下 CPP SDK 的状态查询直接抛异常 (其余 5 语言为静默落 Unknown), 轮询程序 (仿 GUI 观测节奏) 会中断。
  • Python service.py PnetServiceStatus.state 为字符串透传 (ConfigError 原样可读到), 但 docstring (行 150-152 / 380-383) 枚举的服务端状态列表仍为 "Unknown/Stopped/Initialized/DataExchange/Aborted", 缺 ConfigError。
  • GuiClient Models.cs StatusResponse 已补 EverConnected (2026-08-13), 但 StatusMessage 字段 — 服务端 2026-08-14 明确 "GUI/现场据此引导用户在 GUI 改选有效网卡并重载" 的详情文案, GUI 客户端模型不承载, 被未知字段跳过策略丢弃。

影响: 网卡缺失降级发生时, 6 语言 SDK 全部无法如实呈现 ConfigError 态 (多数显示 Unknown, CPP 抛异常); GUI 无法展示缺失网卡名引导。与"错误码/枚举口径与 /api/status 同步"的审计要求直接不符。

修复方向 (待编排, 本审计不修): ① 各语言状态枚举/映射补 ConfigError (SDK 枚举建议追加值, 字符串映射加分支); ② CPP 未识别状态改为落 Unknown 而非抛异常 (或至少对 ConfigError 显式映射); ③ GuiClient StatusResponseStatusMessage; ④ 各语言 docstring 状态列表补 ConfigError。

H2 — 启停响应 errorCode 被 6 语言 SDK 全部丢弃 (服务端 2026-08-13 契约扩展无人消费)

证据:

  • 服务端 PnetApiServer.cs 注释 (行 39-42): "SDK 必须能看到服务端 errorCode — 失败响应一律携带 … SDK 免二次轮询 /api/status"; StartStopResponse.ErrorCode (行 1484) 已带值 (启动失败=原生栈 rc / ConfigError 降级=errAfter)。
  • C# PostServiceStartStop (行 1686-1719): 最小 JSON 解析只读 ok/message, 不读 errorCode; PnetServiceException (PnetEnums.cs:121) 只携带 SDK 侧 PnetErrorCode (ServiceUnreachable/NativeError), 无服务端 errorCode 字段
  • Java postStartStop (行 761-770): StartStopResult{ok,message} — 无 errorCode。
  • Python start()/stop() (行 339-371): 只判 resp.get("ok"), 异常仅带 message — 无 errorCode。
  • Rust start()/stop() (行 390-425): 只取 ok/message — 无 errorCode。
  • C pnet_service.c 启停响应解析 (行 904-920): 只取 ok/message — 无 errorCode。
  • CPP pnet.hpp 启停路径: 同只取 ok/message。

影响: 启动失败 (尤其 ConfigError 降级路径, Ok=false + ErrorCode=errAfter) 时, 调用方只能拿到通用文案 (message 尚含网卡名, 但不含结构化错误码), 数字 errorCode 全部丢失, 需要二次轮询 /api/status 才能取到 — 恰好是服务端明确想省掉的那次轮询。与用户 2026-08-10 "错误码是调用契约的一部分, SDK 必须透出服务端 errorCode" 裁定在启停路径上未落地 (状态查询路径已落地)。

修复方向: ① 各语言启停失败异常/返回值补服务端 errorCode 字段 (C# PnetServiceException 增 ServerErrorCode; C 增 p_error_code 出参; 其余语言同口径); ② 与 H1 修复同波编排 (ConfigError 启动失败的 errorCode 透出是同一链路)。


3. Medium 发现

M1 — 启停超时约定分裂: GUI 15s vs SDK 5s; 且 4 语言无 IO 500ms 分层

证据:

  • GUI GuiClient.cs PostStartStopAsync (行 375-377): 启停单请求 15 秒 CTS 上限, 注释明确 "启停是服务端慢端点 (/api/start 加载 DLL + 驱动配置 + 栈创建可达数秒), 2 秒会把慢启动/慢停止误判成失败; 超时调用方按'服务端可能仍在执行'处理"。
  • C# DarraPnet.cs ServiceHttpTimeoutMs = 5000 (行 347): 启停/探测用 5 秒, 注释 "对齐 PnetAuthorization 15s 口径收紧" — 未对齐 GUI 的 15s 慢端点共识; 若服务端启动超过 5s, C# SDK Start() 会抛 ServiceUnreachable, 而服务端实际仍在启动 (与 GUI 的 15s 防御策略不一致)
  • C pnet_service.h 行 82-87: 5s/500ms 分层 (与 C# 一致)。
  • CPP pnet.hpp 行 1850-1851: 统一 5s, 无 IO 500ms 分层 ("对齐 C# ServiceHttpTimeoutMs 口径" 只对齐了生命周期档)。
  • Python service.py 行 900-901: HTTPConnection(timeout=5) 全部路径 5s, 无分层
  • Java PnetService.http 行 1074-1075: connect 3s / read 5s 无分层
  • Rust service.rs 行 1019-1023: 读写统一 5s 无分层

影响: ① 慢启动场景 (服务端可达数秒) 下 C#/C SDK 可能在 5s 误报失败 (GUI 已为此用 15s); ② C#/C 承诺的 "IO 路径不阻塞 5s (500ms)" 性能契约在 CPP/Java/Python/Rust 四语言未实现 — 服务挂死时这 4 语言的 IO 调用会阻塞至 5s (而非 500ms)。

修复方向: 统一约定: 启停 15s (对齐 GUI 慢端点共识) 或服务端改异步化; IO 路径 500ms 分层 4 语言补齐 (或文档如实降级标注)。

M2 — 服务端新增 ErrDap = -6 未同步到 6 语言 PnetRuntimeErrorCode 枚举

证据:

  • 服务端 PnetNative.cs StackErr (行 733-755): ErrDap = -6 (audit-2026-08-13 "补全与 pnet_stack.h 对齐"); PnetRuntime.cs:2350 已消费 (=> "DAP (槽 0) 插入失败") — 真实可达错误码。
  • C# PnetEnums.cs / Java PnetRuntimeErrorCode.java / Rust service.rs / Python service.py / C pnet_types.h / CPP pnet.hpp 的运行时错误码枚举全部止于 ErrPdi = -5 + Unknown(-10000); 无 -6 → 服务端报 ErrDap 时 6 语言全部落 Unknown (原值仍经 error_code 透传, 影响限于枚举语义, 但枚举契约不同步)。

修复方向: 6 语言枚举各补 ErrDap = -6 (追加, 不重排), 与 H1 同波。

M3 — C# SDK 无 API Key 支持 (其余 5 语言均支持)

证据:

  • 服务端 PnetApiServer 支持可选共享密钥 Api:ApiKey (配置后所有请求须带 X-Darra-Pnet-Api-Key, 恒定时间比较)。
  • C/CPP (api_key 参数) / Java (apiKey) / Python (api_key) / Rust (api_key) 5 语言均实现并文档化。
  • C# 全 SDK 树 grep Api-Key|apiKey|X-Darra = 0 命中DarraPnet.Connect(host, port) 无密钥参数, 请求不带密钥头 → 服务配置了 ApiKey 后 C# SDK 全部请求 401 (映射为 ServiceUnreachable), 无法连接。

修复方向: C# Connect 增可选 apiKey 参数 + 请求头注入 (对齐 5 语言)。

M4 — host 参数文档口径矛盾: 5 语言宣称支持局域网地址, 实际服务仅监听 127.0.0.1

证据:

  • 服务端 PnetApiServer.cs:9 "仅监听本地回环 127.0.0.1, 不暴露局域网" (硬事实)。
  • C# DarraPnet.cs 文档 (行 178-179 等) 明确 "仅本地回环 127.0.0.1 可达, 传局域网地址连接必失败" — 与事实一致。
  • C pnet_service.h:168 / CPP pnet.hpp:1001 / Java PnetService.java:126 / Python service.py:218 / Rust service.rs:326 均写 "127.0.0.1 本地或 192.168.x.x 局域网" — 与事实矛盾, 误导用户传局域网地址必失败。
  • C# README 服务模式示例 (行 98) 甚至给出 DarraPnet.Connect("192.168.1.50", 18840) 作为合法用法 (与同文件 SDK 代码注释矛盾)。

修复方向: 5 语言文档统一改为 "仅本地回环 127.0.0.1 (服务未开放局域网监听; host 参数保留仅 API 兼容)"; C# README 删局域网示例。

M5 — 快照缓存重取清空未提交脏区: 节拍内延迟提交的写在快照过期时被静默丢弃 (6 语言一致, 继承 C# P-3/B 基线)

证据:

  • C# EnsureIoSnapshotFresh (行 1776-1784): 节拍过期重取快照时 _snapshotDirty = false; _dirtyRegionStart/-End = -1; _lastFlushTicks = 0不先提交脏区
  • Python _ensure_snapshot_fresh (行 828-836) / Java ensureSnapshotFresh (行 839-849) / Rust ensure_snapshot_fresh (行 935-946) 同逻辑 (注释同文: "新快照落地后脏区作废")。
  • 触发窗口: 写提交后 20ms 节拍内 (FlushIntervalMs 节流期) 的后续写被延迟, 若接下来是读操作且快照缓存已过期 (>20ms), 重取会清掉未提交脏区 — 该写已向调用方返回 Success 却从未上服务端。与代码自身注释 "提交失败不清脏区 (保数据, 下一节拍重试)" 语义矛盾 (网络失败保脏区, 但缓存过期却弃脏区)。

影响: 20ms 级窄窗口的静默丢写, 6 语言一致 (非单语言缺陷, 属共享设计缺口); 对高频写后读场景有真实风险。

修复方向: 重取快照前先 flush_dirty(force=true) (或重取后保留脏区与本地快照合并)。改动需 6 语言同波 + 契约测试钉死。


4. Low 发现

  • L1 Java README.md:125 写服务模式 "HTTP 直连 (JDK 自带 java.net.http)" — 实际实现用 java.net.HttpURLConnection (PnetService.java:1067)。文档与实现不符。
  • L2 C# README.md:99 示例 if (pnet.Start() != PnetErrorCode.Success) { /* 处理 */ } 误导 — 服务模式 Start() 失败是PnetServiceException, 不会返回非 Success (DarraPnet.cs:474-514)。与 ag15 此前发现的 "README 与实际不符" 同类。
  • L3 测试项目/TEST_SDK_CSharp/Program.cs:18 头部注释 "服务不可达 = NativeDllNotFound (如实 FAIL)" — 与 ag17-fix 后实际契约 (Connect 抛 PnetServiceException → catch 退出码 1, 行 82-96) 不符; 头部注释未随 ag17 修复同步。
  • L4 Java PnetService.status() javadoc (行 217) 宣称返回含 STOPPING, 但映射 switch (行 244-262) 无 STOPPING 分支; PnetState.STOPPING(4) 枚举恒不被映射 (死枚举值, 保留未删)。
  • L5 GuiClient ApiErrorResponse (行 473-486) 未解析服务端错误响应的 errorCode 字段 (2026-08-13 服务端已加) — GUI 错误展示不含错误码 (影响低于 SDK 侧, GUI 主要看 message)。
  • L6 GuiClient DiagAddAsync severity 参数文档 (行 270) 只写 "0 故障 / 1 需要维护 / 2 要求维护", 服务端实际接受 0-3 (3=合格化, PNET_DIAG_MAINT_QUALIFIED)。
  • L7 Python PnetServiceStatus docstring / status() docstring 枚举服务端状态缺 ConfigError (与 H1 同源, Python 行为是字符串透传可用, 仅文档过期)。
  • L8 端口 18840 硬编码 8 处 (服务端 FixedPort + GuiClient FixedPort/DefaultBaseUrl + 6 语言各 1 常量), 均注释 "与 PnetApiServer.FixedPort 同值" 但无生成式/编译期强制单源 (版本号有 version/ 目录 + sync-versions.ps1 单源, 端口没有); 改端口需手工同步 8 处。
  • L9 生命周期语义跨语言分裂 (文档各自标注): C# Dispose()/Stop() 会向服务端发 POST /api/stop; Python close() / Java close() / Rust close() 不发 stop ("服务端从站由服务自身生命周期管理") — 同一"关闭会话"动作 6 语言对服务端从站运行态的影响不一致。

5. 已验证一致 (正面结论, 无需整改)

  1. 端点覆盖无 orphan: 服务端 13 端点中, SDK 薄契约消费 9 个 (info/status/io/alarms/start/stop/io 写/ack/ack-all), 6 语言全部落地; config/reload + diag 两端点按用户裁定属 GUI 厚契约 (GuiClient 已实现 ReloadConfigAsync + DiagAdd/Update/RemoveAsync + GetDiagAsync), 属边界设计而非 orphan; 服务端 GET /api/diag 的 GUI 消费方 (GetDiagAsync) 存在。无"服务端有而 SDK/GUI 全无消费"的孤儿端点。
  2. A1 native 直连禁用口径如实: 6 语言 README 均如实标注 native 直连不可用 (D_1003 布局错位), 代码 fail-fast 已核实: C# NotSupportedException / Java UnsupportedOperationException / Python NotImplementedError / C PNET_ERR_NOT_SUPPORTED / CPP PnetException / Rust NotSupported — 与各 README 声明逐条一致。
  3. 探测端点迁移: 6 语言探测均已由 GET / 改为 GET /api/info (audit-2026-08-13, 服务端 Web 静态页占位后), 无语言仍在探测旧端点。
  4. 错误码双枚举区分 (SDK 调用错误码 vs 服务端运行错误码): 6 语言统一落地, 命名区分、语义注释一致; errorCode/connected 状态透传 (GET /api/status) 6 语言一致。
  5. 地址解析单源对齐: I/Q/M/DB 地址、大端序、不强制对齐、DB 仅槽位 1 — 6 语言均以 C# PnetAddress.Parse 为基准逐项对齐, 无私有解析器分裂 (C# 历史私有解析器已删)。
  6. P-3/B 性能契约 (快照缓存 20ms + 脏区合并 20ms): 6 语言均已实现 (M5 为共享设计缺口, 非缺席)。
  7. 版本单源: version/sdk.txt = 1.0.0 + sync-versions.ps1, 各语言 README 引用一致; 服务端 ProductVersion 与 csproj 同值并注释同步要求。
  8. 冒烟测试覆盖: 测试项目/ 下 TEST_SDK_{C,CPP,CSharp,Java,Python,Rust} 6 套冒烟齐备, 服务不可达如实退出码 1 不裸崩 (ag17-fix 已落)。

6. 修复编排建议 (交主线程, 本 agent 不修)

  1. 波次 1 (H1+H2+M2 同源契约同步): 6 语言补 ConfigError 状态映射 + 启停失败 errorCode 透出 + ErrDap=-6 枚举 + GuiClient 补 StatusMessage/错误 errorCode — 单波完成, 6 语言文件域互不重叠可并行。
  2. 波次 2 (M1 超时统一): 启停超时对齐 GUI 15s 共识 (或服务端启动异步化); CPP/Java/Python/Rust 补 IO 500ms 分层 (或文档降级标注)。
  3. 波次 3 (M3/M4/M5 + Low): C# apiKey 参数; 5 语言 host 文档口径; 快照重取前 flush 脏区 (6 语言 + 契约测试); L1-L9 文档修正随各自语言文件域合并。

本节由全栈审计 wave SDK/客户端层 agent 落档 (2026-08-14, 只读审计)。