全栈审计 · 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/info、GET /api/status、GET /api/io、GET /api/alarms、GET /api/diag、POST /api/start、POST /api/stop、POST /api/io、POST /api/config/reload、POST /api/diag、POST /api/alarms/ack、POST /api/alarms/ack-all。
近期服务端契约变更 (SDK 同步检查对象):
- audit-2026-08-12/13:
/api/status增everConnected字段; 全部失败响应增errorCode字段;StartStopResponse增ErrorCode("SDK 必须能看到服务端 errorCode — 失败响应一律携带"; "SDK 免二次轮询 /api/status")。 - audit-2026-08-13:
StackErr增ErrDap = -6(PnetRuntime.cs:2350 已用于错误文案映射)。 - audit-2026-08-14:
PnetDeviceState增ConfigError = 3(网卡缺失优雅降级, 进程存活/控制面可用);/api/status增statusMessage字段 (ConfigError 态携带缺失网卡名+原因);/api/start在 ConfigError 态返回Ok=false + Message(含网卡名) + ErrorCode。
1. 发现统计
| 分级 | 数量 | 编号 |
|---|---|---|
| High | 2 | H1, H2 |
| Medium | 5 | M1, M2, M3, M4, M5 |
| Low | 9 | L1 - L9 |
| 合计 | 16 |
2. High 发现
H1 — 服务端新状态 ConfigError=3 未同步到任何 SDK 状态映射; CPP 最严重 (状态查询直接抛异常)
证据:
- 服务端
PnetStateService.cs:33新增ConfigError = 3(2026-08-14),GET /api/status的state字符串为"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.pyPnetServiceStatus.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 StatusResponse 补 StatusMessage; ④ 各语言 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.csServiceHttpTimeoutMs = 5000(行 347): 启停/探测用 5 秒, 注释 "对齐 PnetAuthorization 15s 口径收紧" — 未对齐 GUI 的 15s 慢端点共识; 若服务端启动超过 5s, C# SDKStart()会抛 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/ JavaPnetRuntimeErrorCode.java/ Rustservice.rs/ Pythonservice.py/ Cpnet_types.h/ CPPpnet.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/ CPPpnet.hpp:1001/ JavaPnetService.java:126/ Pythonservice.py:218/ Rustservice.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) / JavaensureSnapshotFresh(行 839-849) / Rustensure_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
DiagAddAsyncseverity 参数文档 (行 270) 只写 "0 故障 / 1 需要维护 / 2 要求维护", 服务端实际接受 0-3 (3=合格化,PNET_DIAG_MAINT_QUALIFIED)。 - L7 Python
PnetServiceStatusdocstring /status()docstring 枚举服务端状态缺ConfigError(与 H1 同源, Python 行为是字符串透传可用, 仅文档过期)。 - L8 端口 18840 硬编码 8 处 (服务端
FixedPort+ GuiClientFixedPort/DefaultBaseUrl+ 6 语言各 1 常量), 均注释 "与 PnetApiServer.FixedPort 同值" 但无生成式/编译期强制单源 (版本号有version/目录 +sync-versions.ps1单源, 端口没有); 改端口需手工同步 8 处。 - L9 生命周期语义跨语言分裂 (文档各自标注): C#
Dispose()/Stop()会向服务端发POST /api/stop; Pythonclose()/ Javaclose()/ Rustclose()不发 stop ("服务端从站由服务自身生命周期管理") — 同一"关闭会话"动作 6 语言对服务端从站运行态的影响不一致。
5. 已验证一致 (正面结论, 无需整改)
- 端点覆盖无 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 全无消费"的孤儿端点。 - A1 native 直连禁用口径如实: 6 语言 README 均如实标注 native 直连不可用 (D_1003 布局错位), 代码 fail-fast 已核实: C#
NotSupportedException/ JavaUnsupportedOperationException/ PythonNotImplementedError/ CPNET_ERR_NOT_SUPPORTED/ CPPPnetException/ RustNotSupported— 与各 README 声明逐条一致。 - 探测端点迁移: 6 语言探测均已由
GET /改为GET /api/info(audit-2026-08-13, 服务端 Web 静态页占位后), 无语言仍在探测旧端点。 - 错误码双枚举区分 (SDK 调用错误码 vs 服务端运行错误码): 6 语言统一落地, 命名区分、语义注释一致;
errorCode/connected状态透传 (GET /api/status) 6 语言一致。 - 地址解析单源对齐: I/Q/M/DB 地址、大端序、不强制对齐、DB 仅槽位 1 — 6 语言均以 C#
PnetAddress.Parse为基准逐项对齐, 无私有解析器分裂 (C# 历史私有解析器已删)。 - P-3/B 性能契约 (快照缓存 20ms + 脏区合并 20ms): 6 语言均已实现 (M5 为共享设计缺口, 非缺席)。
- 版本单源:
version/sdk.txt= 1.0.0 +sync-versions.ps1, 各语言 README 引用一致; 服务端ProductVersion与 csproj 同值并注释同步要求。 - 冒烟测试覆盖:
测试项目/下 TEST_SDK_{C,CPP,CSharp,Java,Python,Rust} 6 套冒烟齐备, 服务不可达如实退出码 1 不裸崩 (ag17-fix 已落)。
6. 修复编排建议 (交主线程, 本 agent 不修)
- 波次 1 (H1+H2+M2 同源契约同步): 6 语言补
ConfigError状态映射 + 启停失败 errorCode 透出 +ErrDap=-6枚举 + GuiClient 补StatusMessage/错误errorCode— 单波完成, 6 语言文件域互不重叠可并行。 - 波次 2 (M1 超时统一): 启停超时对齐 GUI 15s 共识 (或服务端启动异步化); CPP/Java/Python/Rust 补 IO 500ms 分层 (或文档降级标注)。
- 波次 3 (M3/M4/M5 + Low): C# apiKey 参数; 5 语言 host 文档口径; 快照重取前 flush 脏区 (6 语言 + 契约测试); L1-L9 文档修正随各自语言文件域合并。
本节由全栈审计 wave SDK/客户端层 agent 落档 (2026-08-14, 只读审计)。