Open
feat: 增加受限 PCIe PLDM responder 与 Base 应答 #13
liuxingsenAdd on 13 days ago
feat: 增加受限 PCIe PLDM responder 与 Base 应答 #13
Open
liuxingsenAdd on 13 days ago
Total: 16 files changed+1040-29
@@ -1,5 +1,8 @@
1# Changelog1# Changelog
2All notable changes to this project will be documented in this file.2All notable changes to this project will be documented in this file.
3+## [1.130.18] - 2026-10-26
4+新增默认关闭的 PCIe PLDM responder、独立回复上下文和 Base 能力注册,完善请求编解码;公共应答仅接收已登记PLDM端点,不能借用NC-SI注册授权。
5+ 
3## [1.130.15] - 2026-08-256## [1.130.15] - 2026-08-25
4更新版本号,触发流水线推包7更新版本号,触发流水线推包
5 8 
@@ -0,0 +1,25 @@
1+# 受限 PCIe PLDM 基础应答
2+ 
3+`CONFIG_MCTP_EP_RESPONDER` 默认关闭;启用后只接受已登记 PCIe 对端的普通请求,SMBus、未知来源和 Datagram 不进入新增业务。非法配置关闭新增responder且不阻断既有MCTP启动。`PLDM_TID` 为稳定产品配置(1~254),不随 EID 改变。Base 支持 GetTID/GetPLDMVersion/GetPLDMTypes/GetPLDMCommands,位图与注册表一致;默认仅宣告Base;安装PR !12的取时模块后再启用`CONFIG_MCTP_PLDM_TIME`,按实际注册发布Type3/命令12;依赖缺失或注册失败时关闭responder且不影响既有MCTP。
4+ 
5+每次回复独立保存路由、Tag、IID、Type、Command及整条上下文链,同来源/Tag不覆盖,全局最多64项;满额拒绝新请求且保留旧事务,`overload_drops`累计拒绝数,首次拒绝记录DEBUG避免洪泛日志。当前无独立有界错误发送队列,不绕过配额发送错误帧;对端依协议超时/退避重试。注销、重建、地址变化、重复或迟到完成均失效;250ms巡检清理闲置项,实际有效性仍在完成时按命令期限判定。新旧配置的巡检回调隔离。`push_checked`仅返回同步协议链提交结果,不等于硬件异步完成。首次complete即领取回复资格,非法完成码/响应类型/长度也释放pending并拒绝后续完成,同Tag重传可重新受理。收包TO直接来自线上头,表示报文源拥有Tag;正常响应TO=0,原requester为Tag所有者不改变此位语义(DSP0236 1.3.0表1)。
6+ 
7+本PR包含请求头、首字节、最短错误响应及IC=1拒收修复。独立分支覆盖响应生命周期、Base能力/CRC/错误码和旧PLDM查询;完整候选4620c09另有43000四网卡44次真实应答记录,不冒充本分支已独立部署。上线前须集成共享传输层修复;完整接口代次、统一TX、资源压力及阶段2仍待完成。
8+ 
9+## 公共查询接入
10+ 
11+GetTID(Type=0、Command=2)查询的是BMC配置的PLDM Terminus ID,不是网卡EID。
12+GetDateTime(Type=3、Command=12)依赖!12的真实时间服务;两个命令统一在本应答器处理,
13+不在网卡驱动中重复建立BMC公共业务。SP888独立驱动通过component_drivers !1469登记类型1端点。
14+ 
15+准入必须匹配PCIe介质、来源EID、物理地址和PLDM消息类型。此前仅检查前三项会允许
16+只有NC-SI端点的来源进入PLDM公共业务;现已限制 `get_msg_type()==MCTP_MESSAGE_TYPE_PLDM`。
17+完成回复时仍核对原端点对象,撤销或替换后不能将旧应答交给新对象。
18+ 
19+qemu_mockup的独立`gettid`、`datetime`场景可发起公共查询。GetTID请求示例为
20+`01 80 00 02`,配置TID=1时响应为`01 00 00 02 00 01`(包含MCTP类型,不含传输头)。
21+取时请求为`01 80 03 0c`;回复必须保留请求Tag/IID并置TO=0、RQ=0。
22+验证应保存真实双向报文;不能把旧`Send`发送的新请求当成应答,参考端成功不属于BMC验收。
23+ 
24+GetTID协议允许返回0表示尚未分配;本产品启用responder的配置策略仍要求PLDM_TID为1~254。
25+仿真客户端会如实显示0及assigned=false,不把合法未分配值误判成保留值255。
@@ -2,7 +2,7 @@
2 "name": "mctpd",2 "name": "mctpd",
3 "type": "application",3 "type": "application",
4 "deployConfig": "framework.service",4 "deployConfig": "framework.service",
5- "version": "1.130.15",5+ "version": "1.130.18",
6 "author": "Huawei",6 "author": "Huawei",
7 "license": "Mulan PSL v2",7 "license": "Mulan PSL v2",
8 "options": {8 "options": {
@@ -166,6 +166,8 @@ static void register_upper_ctxs(lua_State *L, luawrap::stack_table &t)
166 cls.def_readwrite("pldm_type", &pldm_ctx::pldm_type);166 cls.def_readwrite("pldm_type", &pldm_ctx::pldm_type);
167 cls.def_readwrite("command_code", &pldm_ctx::command_code);167 cls.def_readwrite("command_code", &pldm_ctx::command_code);
168 cls.def_readwrite("rq", &pldm_ctx::rq);168 cls.def_readwrite("rq", &pldm_ctx::rq);
169+ cls.def_readwrite("d", &pldm_ctx::d);
170+ cls.def_readwrite("completion_code", &pldm_ctx::completion_code);
169 cls.def_readwrite("iid", &pldm_ctx::iid);171 cls.def_readwrite("iid", &pldm_ctx::iid);
170 t.set("pldm_ctx", cls);172 t.set("pldm_ctx", cls);
171 });173 });
@@ -284,6 +286,16 @@ static void protocol_push(lua_State *L, T *proto, const slice_t &data, std::opti
284 }286 }
285}287}
286 288 
289+template <typename T>
290+static bool protocol_push_checked(lua_State *L, T *proto, const slice_t &data, std::optional<const protocol_ctx *> ctx,
291+ const uint32_t medium_type)
292+{
293+ if (ctx) {
294+ return proto->push_data(**ctx, data, medium_type);
295+ }
296+ return proto->push_data(protocol_ctx(), data, medium_type);
297+}
298+ 
287template <typename T>299template <typename T>
288static void register_protocol(lua_State *L, std::function<void(lua_class<T, protocol> &cls)> &&f)300static void register_protocol(lua_State *L, std::function<void(lua_class<T, protocol> &cls)> &&f)
289{301{
@@ -298,6 +310,7 @@ static void register_protocol(lua_State *L, std::function<void(lua_class<T, prot
298 cls.set("set_capture_handler", c_func_wrap(L, set_capture_handler<T>));310 cls.set("set_capture_handler", c_func_wrap(L, set_capture_handler<T>));
299 cls.set("pop", c_func_wrap(L, protocol_pop<T>));311 cls.set("pop", c_func_wrap(L, protocol_pop<T>));
300 cls.set("push", c_func_wrap(L, protocol_push<T>));312 cls.set("push", c_func_wrap(L, protocol_push<T>));
313+ cls.set("push_checked", c_func_wrap(L, protocol_push_checked<T>));
301 cls.def_property("name", c_func_wrap(L, T::get_name), c_func_wrap(L, T::set_name));314 cls.def_property("name", c_func_wrap(L, T::get_name), c_func_wrap(L, T::set_name));
302 f(cls);315 f(cls);
303}316}
@@ -54,8 +54,7 @@ struct pldm_rsp_pkg_hdr {
54using pldm_req_payload_hdr = mct_payload_hdr<pldm_req_pkg_hdr>;54using pldm_req_payload_hdr = mct_payload_hdr<pldm_req_pkg_hdr>;
55using pldm_rsp_payload_hdr = mct_payload_hdr<pldm_rsp_pkg_hdr>;55using pldm_rsp_payload_hdr = mct_payload_hdr<pldm_rsp_pkg_hdr>;
56 56 
57-const int MCTP_PLDM_HEADER_VERSION = 0;57+const int MCTP_PLDM_HEADER_VERSION = 0;
58-const int MCTP_PLDM_COMPLETION_CODE = 0x0;
59 58 
60} // namespace Mctp59} // namespace Mctp
61 60 
@@ -67,34 +66,55 @@ pldm_protocol::~pldm_protocol()
67 66 
68bool pldm_protocol::pop(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type)67bool pldm_protocol::pop(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type)
69{68{
70- if (data.size() < sizeof(pldm_rsp_payload_hdr)) {69+ if (data.size() < sizeof(pldm_req_payload_hdr)) {
71 return false;70 return false;
72 }71 }
73 72 
74- const pldm_rsp_payload_hdr &hdr = *reinterpret_cast<const pldm_rsp_payload_hdr *>(data.data());73+ const pldm_req_payload_hdr &hdr = *reinterpret_cast<const pldm_req_payload_hdr *>(data.data());
75 if (hdr.mctp_hdr.msg_type != MCTP_MESSAGE_TYPE_PLDM) {74 if (hdr.mctp_hdr.msg_type != MCTP_MESSAGE_TYPE_PLDM) {
76 return false;75 return false;
77 }76 }
77+ if (hdr.mctp_hdr.ic) {
78+ debug_log(DLOG_DEBUG, "pldm_protocol: unsupported integrity check, medium=%u, size=%zu", medium_type,
79+ data.size());
80+ return false;
81+ }
82+ if (hdr.pkg_hdr.hdr_ver != MCTP_PLDM_HEADER_VERSION || (!hdr.pkg_hdr.rq && hdr.pkg_hdr.d)) {
83+ return false;
84+ }
85+ const size_t header_size = hdr.pkg_hdr.rq ? sizeof(pldm_req_payload_hdr) : sizeof(pldm_rsp_payload_hdr);
86+ if (data.size() < header_size) {
87+ return false;
88+ }
78 89 
79 pldm_ctx local_ctx(&ctx);90 pldm_ctx local_ctx(&ctx);
80- local_ctx.pldm_type = hdr.pkg_hdr.pldm_type;91+ local_ctx.pldm_type = hdr.pkg_hdr.pldm_type;
81- local_ctx.command_code = hdr.pkg_hdr.command_code;92+ local_ctx.command_code = hdr.pkg_hdr.command_code;
82- local_ctx.rq = hdr.pkg_hdr.rq;93+ local_ctx.rq = hdr.pkg_hdr.rq;
83- local_ctx.iid = hdr.pkg_hdr.iid;94+ local_ctx.d = hdr.pkg_hdr.d;
84- local_ctx.completion_code = hdr.pkg_hdr.completion_code;95+ local_ctx.iid = hdr.pkg_hdr.iid;
85- return protocol::pop(local_ctx, slice(data, sizeof(pldm_rsp_payload_hdr)), medium_type);96+ if (!local_ctx.rq) {
97+ local_ctx.completion_code = data[sizeof(pldm_req_payload_hdr)];
98+ }
99+ return protocol::pop(local_ctx, data.substr(header_size), medium_type);
86}100}
87 101 
88bool pldm_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type)102bool pldm_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uint32_t medium_type)
89{103{
90 pldm_ctx local_ctx = ctx.cast<pldm_ctx>();104 pldm_ctx local_ctx = ctx.cast<pldm_ctx>();
105+ if (local_ctx.rq > 1 || local_ctx.d > 1 || (!local_ctx.rq && local_ctx.d) || local_ctx.iid > 31 ||
106+ local_ctx.pldm_type > 63) {
107+ debug_log(DLOG_ERROR, "pldm_protocol: invalid TX header");
108+ return false;
109+ }
atomgit-bot
atomgit-botatomgit-bot13 days ago

🔵 Low Priority

变更行(pldm_protocol.cpp:105-109)新增了发送侧校验并返回 false: if (local_ctx.rq > 1 || local_ctx.d > 1 || (!local_ctx.rq && local_ctx.d) || local_ctx.iid > 31 || local_ctx.pldm_type > 63) { debug_log(DLOG_ERROR, "pldm_protocol: invalid TX header"); return false; } 影响契约:本 PR 同时新增了 push_checked(l_mctp.cpp:289-297)用于把提交结果回传 Lua,但既有 requester 路径仍用旧的 push——ep_pldm.lua:54 与 :75 的 self.mctp_engine.pldm_proto:push(payload, self.pldm_ctx, msg_key),而 protocol_push(l_mctp.cpp:278-287)丢弃返回值。

失败模式:改动前非法头字段会被位域静默截断后照常发出;改动后会直接不发送。PldmType/CommandCode 来自上层 req_ctx(ep_pldm.lua:95-96 的 tonumber),一旦调用方传入 PldmType=64+、iid 越界或 d/rq 组合非法(例如上游把 d 置 1 却发响应),帧被静默丢弃,request() 只能等满默认 15s 超时(MCTP_REQUEST_DEFAULT_TIMEOUT)才返回 E_TIMEOUT,日志里也只有在 DLOG_ERROR 级别的一行,问题会被误判为对端无响应。属于本 diff 新增校验带来的行为变化。

修复方向:让 requester 路径也使用 push_checked 并据返回值立即返回错误(send 直接报错、request 提前唤醒为发送失败),或至少在 ep_pldm 调用处检查返回值并记录带 eid/msg_key 的错误日志。

建议:在 ep_pldm 的 send/request 中改用 push_checked 并根据返回值立即失败(而非静默丢弃后等 15s 超时),或至少在调用处检查 push 结果并记录含 eid/msg_key 的错误日志。

likedislike
Incorrect?
liuxingsen
13 days ago Comments:
91 110 
92- m_tx_buf.reserve(sizeof(pldm_req_payload_hdr) + data.size());111+ m_tx_buf.reserve(sizeof(pldm_rsp_payload_hdr) + data.size());
93 m_tx_buf.resize(sizeof(pldm_req_payload_hdr));112 m_tx_buf.resize(sizeof(pldm_req_payload_hdr));
94 pldm_req_payload_hdr &hdr = *reinterpret_cast<pldm_req_payload_hdr *>(&m_tx_buf[0]);113 pldm_req_payload_hdr &hdr = *reinterpret_cast<pldm_req_payload_hdr *>(&m_tx_buf[0]);
95 (void)memset_s(&hdr, sizeof(hdr), 0, sizeof(hdr));114 (void)memset_s(&hdr, sizeof(hdr), 0, sizeof(hdr));
96 115 
97 hdr.pkg_hdr.rq = local_ctx.rq;116 hdr.pkg_hdr.rq = local_ctx.rq;
117+ hdr.pkg_hdr.d = local_ctx.d;
98 hdr.pkg_hdr.iid = local_ctx.iid;118 hdr.pkg_hdr.iid = local_ctx.iid;
99 119 
100 hdr.pkg_hdr.hdr_ver = MCTP_PLDM_HEADER_VERSION;120 hdr.pkg_hdr.hdr_ver = MCTP_PLDM_HEADER_VERSION;
@@ -104,6 +124,9 @@ bool pldm_protocol::push(const protocol_ctx &ctx, const slice_t &data, const uin
104 124 
105 hdr.mctp_hdr.msg_type = MCTP_MESSAGE_TYPE_PLDM;125 hdr.mctp_hdr.msg_type = MCTP_MESSAGE_TYPE_PLDM;
106 126 
127+ if (!local_ctx.rq) {
128+ m_tx_buf.push_back(local_ctx.completion_code);
129+ }
107 m_tx_buf.insert(m_tx_buf.end(), data.begin(), data.end());130 m_tx_buf.insert(m_tx_buf.end(), data.begin(), data.end());
108 131 
109 return protocol::push(ctx, slice(m_tx_buf), medium_type);132 return protocol::push(ctx, slice(m_tx_buf), medium_type);
@@ -29,18 +29,19 @@ struct pldm_ctx : public protocol_ctx {
29 bool visit(const ctx_vistor_t &h) const override29 bool visit(const ctx_vistor_t &h) const override
30 {30 {
31 if (!h(this, "completion_code", completion_code) || !h(this, "command_code", command_code) ||31 if (!h(this, "completion_code", completion_code) || !h(this, "command_code", command_code) ||
32- !h(this, "rq", rq) || !h(this, "iid", iid) || !h(this, "pldm_type", pldm_type)) {32+ !h(this, "rq", rq) || !h(this, "d", d) || !h(this, "iid", iid) || !h(this, "pldm_type", pldm_type)) {
33 return false;33 return false;
34 }34 }
35 35 
36 return protocol_ctx::visit(h);36 return protocol_ctx::visit(h);
37 }37 }
38 38 
39- uint8_t completion_code;39+ uint8_t completion_code = 0;
40- uint8_t command_code;40+ uint8_t command_code = 0;
41- uint8_t rq;41+ uint8_t rq = 0;
42- uint8_t iid;42+ uint8_t d = 0;
43- uint8_t pldm_type;43+ uint8_t iid = 0;
44+ uint8_t pldm_type = 0;
44};45};
45 46 
46class pldm_protocol : public protocol {47class pldm_protocol : public protocol {
@@ -10,4 +10,9 @@
10 10 
11local cfg = { CHECK_MCTP_TIMES = 3, CHECK_MCTP_TIMEOUT = 3000, CHECK_MCTP_INTERVAL = 1000 }11local cfg = { CHECK_MCTP_TIMES = 3, CHECK_MCTP_TIMEOUT = 3000, CHECK_MCTP_INTERVAL = 1000 }
12 12 
13+-- 产品验证后启用;TID 属于稳定产品配置,不能直接采用动态 MCTP EID。
14+cfg.CONFIG_MCTP_EP_RESPONDER = false
15+cfg.CONFIG_MCTP_PLDM_TIME = false
16+cfg.PLDM_TID = 1
17+ 
13return cfg18return cfg
@@ -76,8 +76,9 @@ function c_pldm_endpoint:send(ctx, data, req_ctx)
76 end)76 end)
77end77end
78 78 
79-local function check_ctx_value(val)79+local function check_ctx_value(val, maximum)
80- return val and type(val) == 'string' and #val > 080+ local num = type(val) == 'string' and tonumber(val)
81+ return num and num % 1 == 0 and num >= 0 and num <= maximum
81end82end
82 83 
83function c_pldm_endpoint:get_iid()84function c_pldm_endpoint:get_iid()
@@ -87,7 +88,7 @@ function c_pldm_endpoint:get_iid()
87end88end
88 89 
89function c_pldm_endpoint:prepare_pldm_request(req_ctx, rsp_ctx, data)90function c_pldm_endpoint:prepare_pldm_request(req_ctx, rsp_ctx, data)
90- if not (check_ctx_value(req_ctx.CommandCode) and check_ctx_value(req_ctx.PldmType)) then91+ if not (check_ctx_value(req_ctx.CommandCode, 255) and check_ctx_value(req_ctx.PldmType, 63)) then
91 error(errors.invalid_mctp_request())92 error(errors.invalid_mctp_request())
92 end93 end
93 94 
@@ -105,6 +106,8 @@ function c_pldm_endpoint:prepare_pldm_request(req_ctx, rsp_ctx, data)
105 pldm_ctx.iid = iid106 pldm_ctx.iid = iid
106 -- pldm请求的rq为1107 -- pldm请求的rq为1
107 pldm_ctx.rq = 1108 pldm_ctx.rq = 1
109+ -- 普通查询不能继承上次接收通知的 Datagram 标志。
110+ pldm_ctx.d = 0
108 111 
109 -- msg_type, eid和medium_type组合成key, 方便在tx_handler可以根据key找到对应的ep112 -- msg_type, eid和medium_type组合成key, 方便在tx_handler可以根据key找到对应的ep
110 local msg_key = self:make_msg_key(MCTP_MESSAGE_TYPE_PLDM)113 local msg_key = self:make_msg_key(MCTP_MESSAGE_TYPE_PLDM)
@@ -112,12 +115,12 @@ function c_pldm_endpoint:prepare_pldm_request(req_ctx, rsp_ctx, data)
112 return data, iid, msg_key115 return data, iid, msg_key
113end116end
114 117 
115-function c_pldm_endpoint:process(ctx, data)118+function c_pldm_endpoint:process(_, data)
116- -- 暂时bmc不处理设备发来的pldm请求119+ local pldm_ctx = self.pldm_ctx
117- if ctx.rq == 1 then120+ -- 收包TO表示报文源拥有Tag,不是本端拥有Tag;正常响应必须TO=0/RQ=0。
121+ if pldm_ctx.rq == 1 or self.mctp_ctx.tag_owner then
O
Oopenubmc-ci-bot13 days ago

⚠️ 警告:新增的 tag_owner 条件可能吞掉本端 requester 的正常响应

ep_pldm.lua:121 在原有 rq == 1 判断基础上新增了 or self.mctp_ctx.tag_owner。在 MCTP 请求/响应模型中,发起请求的一端(如 BMC 作为 requester)正是该报文的 tag owner;当对端返回响应时,收到响应的上下文 mctp_ctx.tag_owner 很可能仍为 true。此时该响应会被 return true 消费掉,走不到 wakeup_request(self:make_rsp_id(iid), data),原本依赖旧 requester 路径的请求就会一直等到超时。

该新增分支与测试用例 test_only_response_direction_can_complete_request 的 owner = true 分支行为一致(期望保持 waiting),说明是刻意为之。但 PR 描述提及需回归“同 Tag 双向”,建议确认:普通设备主动请求后的响应、以及旧 requester 收发路径(wakeup_request)是否在本端为 tag owner 时被正确保留,避免响应被误吞导致业务超时。

修复建议:

  • 明确 tag_owner 在收到的响应报文中的语义;若它是“对端是否是 tag owner”而非“本端是否为 owner”,则此条件方向可疑。
  • 仅对确实不受理的请求(如 Datagram、方向错误)做 return true,响应方向仍应交付旧 requester 完成请求。
likedislike
liuxingsen
13 days ago Comments:
118 return true122 return true
119 end123 end
120- local pldm_ctx = self.pldm_ctx
121 local iid = pldm_ctx.iid124 local iid = pldm_ctx.iid
122 self.mctp_engine:wakeup_request(self:make_rsp_id(iid), data)125 self.mctp_engine:wakeup_request(self:make_rsp_id(iid), data)
123 return true126 return true
@@ -51,6 +51,46 @@ local E_OK = mctp_def.E_OK
51 51 
52local c_mctp_engine = c_class()52local c_mctp_engine = c_class()
53 53 
54+function c_mctp_engine:enable_pldm_responder()
55+ if not self.pldm_responder then
56+ self.pldm_responder = require('pldm_responder').new(self)
57+ end
58+ return self.pldm_responder
59+end
60+ 
61+function c_mctp_engine:configure_pldm_responder(config)
62+ if self.pldm_reply_timer then
63+ stop_timer(self.pldm_reply_timer)
64+ self.pldm_reply_timer = nil
65+ end
66+ if self.pldm_responder then
67+ self.pldm_responder:reset()
68+ end
69+ self.pldm_responder = nil
70+ if config.CONFIG_MCTP_EP_RESPONDER then
71+ local responder = self:enable_pldm_responder()
72+ local ok = pcall(function()
73+ responder:register_base(config.PLDM_TID)
74+ if config.CONFIG_MCTP_PLDM_TIME then
75+ require('pldm_time').register(responder, self.bus)
76+ end
77+ end)
78+ if not ok then
79+ log:error('pldm_responder: invalid configuration; responder disabled')
80+ self.pldm_responder = nil
81+ return
82+ end
83+ local function expire()
84+ if self.pldm_responder ~= responder then
85+ return
86+ end
87+ responder:expire()
88+ self.pldm_reply_timer = c_timer.new(250, expire)
89+ end
90+ self.pldm_reply_timer = c_timer.new(250, expire)
91+ end
92+end
93+ 
54function c_mctp_engine:pcie_binding_params_set()94function c_mctp_engine:pcie_binding_params_set()
55 self.mctp_proto:set_binding_id(MCTP_OVER_PCIE_ID)95 self.mctp_proto:set_binding_id(MCTP_OVER_PCIE_ID)
56end96end
@@ -233,12 +273,14 @@ function c_mctp_engine:start()
233 end)273 end)
234 274 
235 self.hw_pcie_proto:vdm_engine_reinit()275 self.hw_pcie_proto:vdm_engine_reinit()
276+ self:configure_pldm_responder(require('mctp_config'))
236 self:register_control()277 self:register_control()
237 self:start_worker(fd)278 self:start_worker(fd)
238 log:notice('[System%s]mctp_engine: open mctp device success', self.system_id)279 log:notice('[System%s]mctp_engine: open mctp device success', self.system_id)
239end280end
240 281 
241function c_mctp_engine:stop()282function c_mctp_engine:stop()
283+ self:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = false })
242 self.pcie_read_work:stop()284 self.pcie_read_work:stop()
243 self.pcie_read_work:join()285 self.pcie_read_work:join()
244 self.pcie_read_work = nil286 self.pcie_read_work = nil
@@ -260,6 +302,9 @@ function c_mctp_engine:reset()
260end302end
261 303 
262function c_mctp_engine:reinit()304function c_mctp_engine:reinit()
305+ if self.pldm_responder then
306+ self.pldm_responder:reset()
307+ end
263 log:notice('[System%s]mctp_engine: vdm engine reinit', self.system_id)308 log:notice('[System%s]mctp_engine: vdm engine reinit', self.system_id)
264 self.hw_pcie_proto:vdm_engine_reinit()309 self.hw_pcie_proto:vdm_engine_reinit()
265end310end
@@ -325,6 +370,9 @@ end
325 370 
326function c_mctp_engine:set_bmc_eid(eid, phy_addr)371function c_mctp_engine:set_bmc_eid(eid, phy_addr)
327 if self.bmc_eid ~= eid or self.bmc_phy_addr ~= phy_addr then372 if self.bmc_eid ~= eid or self.bmc_phy_addr ~= phy_addr then
373+ if self.pldm_responder then
374+ self.pldm_responder:reset()
375+ end
328 self.bmc_eid = eid376 self.bmc_eid = eid
329 self.mctp_proto.pcie_eid = eid377 self.mctp_proto.pcie_eid = eid
330 self.bmc_phy_addr = phy_addr378 self.bmc_phy_addr = phy_addr
@@ -480,6 +528,13 @@ function c_mctp_engine:rx(ctx, data, msg_type, medium_type)
480 if not msg_type then528 if not msg_type then
481 return false529 return false
482 end530 end
531+ if
532+ msg_type == MCTP_MESSAGE_TYPE_PLDM
533+ and self.pldm_responder
534+ and self.pldm_responder:receive(ctx, data, medium_type)
535+ then
536+ return true
537+ end
483 mctp_ctx:copy_from(ctx)538 mctp_ctx:copy_from(ctx)
484 local src_eid = mctp_ctx.src539 local src_eid = mctp_ctx.src
485 local ep = self.endpoints:get_endpoint(src_eid, msg_type, medium_type)540 local ep = self.endpoints:get_endpoint(src_eid, msg_type, medium_type)
@@ -0,0 +1,100 @@
1+-- Copyright (c) 2026 Huawei Technologies Co., Ltd.
2+-- openUBMC is licensed under Mulan PSL v2.
3+-- You can use this software according to the terms and conditions of the Mulan PSL v2.
4+-- You may obtain a copy of Mulan PSL v2 at:
5+-- http://license.coscl.org.cn/MulanPSL2
6+-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
7+-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
8+-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
9+-- See the Mulan PSL v2 for more details.
10+ 
11+-- SPDX-License-Identifier: MulanPSL-2.0
12+local M = {}
13+ 
14+local function bitmap(items, bytes)
15+ local result = {}
16+ for i = 1, bytes do
17+ result[i] = 0
18+ end
19+ for bit in pairs(items) do
20+ local index = bit // 8 + 1
21+ result[index] = result[index] | (1 << (bit % 8))
22+ end
23+ return string.char(table.unpack(result))
24+end
25+ 
26+local function capabilities(responder)
27+ local types = {}
28+ for _, handler in pairs(responder.handlers) do
29+ types[handler.pldm_type] = types[handler.pldm_type] or { version = handler.version, commands = {} }
30+ types[handler.pldm_type].commands[handler.command] = true
31+ end
32+ return types
33+end
34+ 
35+local function crc32(data)
36+ local crc = 0xffffffff
37+ for i = 1, #data do
38+ crc = crc ~ data:byte(i)
39+ for _ = 1, 8 do
40+ local low = crc & 1
41+ crc = crc >> 1
42+ if low ~= 0 then
43+ crc = crc ~ 0xedb88320
44+ end
45+ end
46+ end
47+ return crc ~ 0xffffffff
48+end
49+ 
50+function M.register(responder, tid)
51+ assert(type(tid) == 'number' and tid % 1 == 0 and tid >= 1 and tid <= 254, 'Invalid configured PLDM TID')
52+ local function register(command, size, rsp_size, dispatch)
53+ return responder:register({
54+ pldm_type = 0,
55+ version = 0xF1F1F000,
56+ command = command,
57+ min_req = size,
58+ max_req = size,
59+ max_rsp = rsp_size,
60+ timeout_ms = 50,
61+ dispatch = dispatch,
62+ })
63+ end
64+ register(2, 0, 1, function(_, done)
65+ done(0, string.char(tid))
66+ end)
67+ register(4, 0, 8, function(_, done)
68+ done(0, bitmap(capabilities(responder), 8))
69+ end)
70+ register(3, 6, 13, function(data, done)
71+ -- DSP0240 1.1.0 表10:handle[0:3]、flag[4](First=1/Next=0)、type[5]。
72+ local flag, typ = data:byte(5, 6)
O
Oopenubmc-ci-bot13 days ago

🚨 严重:GetPLDMVersion 误读 transferOpFlag 字节偏移

GetPLDMVersion(0x03) 请求负载布局为:byte1 = transferOpFlag,bytes 2-5 = DataTransferHandle,byte6 = PLDMType。但 data:byte(5, 6) 把 byte5(DataTransferHandle 的最后一个字节)当作 flag,把 byte6(PLDMType)当作 typ,transferOpFlag 实际从未被正确读取。

一个符合规范的客户端发送 transferOpFlag=1, handle=0, type=0(即 \x01\x00\x00\x00\x00\x00)时,data:byte(5) 为 0,flag 误判为 0,会返回 0x80(GetNextPart 无有效句柄)而不是正常返回版本信息,导致 GetFirstPart 请求恒失败。

修复建议:

local flag = data:byte(1)
local typ  = data:byte(6)

⚠️ 当前单测 test_base_version_ignores_first_handle_and_validates_next(test_pldm_responder.lua)发送 \x01\x87\x00\x03\xff\xff\xff\xff\x01\x00 之所以能通过,正是因为句柄最后一位恰好为 \x01,掩盖了此偏移错误;请求首字节实际填的是非法的 \xff(不是 GetFirstPart=1)。建议补充一个 handle=0, flag=1 的用例来暴露该问题。

likedislike
liuxingsen
13 days ago Comments:
73+ if flag ~= 0 and flag ~= 1 then
74+ return done(0x81, '')
75+ end
76+ -- 只提供单版本单包,GetNextPart 不存在有效句柄;首包忽略输入句柄。
77+ if flag == 0 then
78+ return done(0x80, '')
79+ end
atomgit-bot
atomgit-botatomgit-bot13 days ago

🟡 Medium Priority

变更行(pldm_base.lua 70-85,注册 Base 命令 3 = GetPLDMVersion): local flag, typ = data:byte(5, 6) if flag ~= 0 and flag ~= 1 then return done(0x81, '') end -- 只提供单版本单包,GetNextPart 不存在有效句柄;首包忽略输入句柄。 if flag == 0 then return done(0x80, '') end ... return done(0, string.pack('<I4B', 0, 5) .. version .. ...) 影响契约:DSP0240 中 GetPLDMVersion/GetPLDMCommands 请求的 TransferFlag 定义为 0x00 = GetFirstPart、0x01 = GetNextPart(请求方第一次取版本必须发 0x00)。本实现却把 flag==0 判为“无有效句柄”直接返回 0x80,把 flag==1(GetNextPart)当作首包返回完整的 StartAndEnd(0x05) 响应。

失败模式:任何符合规范的请求方(主机固件/PLDM 栈启动时枚举 BMC 的 PLDM 版本,这是 Base 的必备命令)发出的第一个请求就是 GetFirstPart(0x00),必然收到 0x80 错误,永远拿不到版本;而实现只会对“续传”请求返回 StartAndEnd,逻辑上自相矛盾(NextDataTransferHandle 恒为 0,续传请求本不应存在)。特性一旦由产品打开(CONFIG_MCTP_EP_RESPONDER=true),PLDM 版本协商即整体失败。

修复方向:把两分支语义对调——flag==0(GetFirstPart)忽略输入句柄并返回 <I4B>(0, 0x05)..version..crc;flag==1(GetNextPart)返回“无效数据传输句柄”错误码。同步更新 test/unit/test_pldm_responder.lua:249-262 中的用例(当前第 250 行用 flag=1 断言成功、第 254 行用 flag=0 断言 0x80,正是把这个错误映射固化进了测试)。

建议:按 DSP0240 对调 TransferFlag 语义:flag==0(GetFirstPart) 忽略 DataTransferHandle 并返回 StartAndEnd 的 13 字节响应;flag==1(GetNextPart) 返回无效句柄错误码;同时修正 test_pldm_responder.lua 中对应用例,并复核 0x80/0x81/0x83/0x84 取值是否符合规范完成码表。

likedislike
Incorrect?
liuxingsen
13 days ago Comments:
80+ local capability = capabilities(responder)[typ]
81+ if not capability then
82+ return done(0x83, '')
83+ end
84+ local version = string.pack('<I4', capability.version)
85+ return done(0, string.pack('<I4B', 0, 5) .. version .. string.pack('<I4', crc32(version)))
86+ end)
87+ register(5, 5, 32, function(data, done)
88+ local typ, version = string.unpack('<BI4', data)
89+ local capability = capabilities(responder)[typ]
90+ if not capability then
91+ return done(0x83, '')
92+ end
93+ if version ~= capability.version then
94+ return done(0x84, '')
95+ end
96+ return done(0, bitmap(capability.commands, 32))
97+ end)
98+end
99+ 
100+return M
@@ -0,0 +1,219 @@
1+-- Copyright (c) 2026 Huawei Technologies Co., Ltd.
2+-- openUBMC is licensed under Mulan PSL v2.
3+-- You can use this software according to the terms and conditions of the Mulan PSL v2.
4+-- You may obtain a copy of Mulan PSL v2 at:
5+-- http://license.coscl.org.cn/MulanPSL2
6+-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
7+-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
8+-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
9+-- See the Mulan PSL v2 for more details.
10+ 
11+-- SPDX-License-Identifier: MulanPSL-2.0
12+local class = require('mc.class')
13+local mctp = require('mctp')
14+local def = require('mctp_def')
15+local MCTP_MESSAGE_TYPE_PLDM = require('mctp_lib').MCTP_MESSAGE_TYPE_PLDM
16+local clock = require('utils.core')
17+local log = require('mc.logging')
18+ 
19+local MAX_PLDM_REQ = 4092 -- 4096B消息减去MCTP类型与PLDM三字节头
20+local responder = class()
21+local function integer(value, low, high)
22+ return type(value) == 'number' and value % 1 == 0 and value >= low and value <= high
23+end
24+ 
25+function responder:ctor(engine)
26+ self.engine, self.handlers, self.pending = engine, {}, {}
27+ self.count, self.reg_count, self.generation, self.overload_drops = 0, 0, 0, 0
28+ self.now = function()
29+ local time = clock.monotonic_time()
30+ return time.tv_sec * 1000 + time.tv_nsec // 1000000
31+ end
32+end
33+ 
34+function responder:register(desc)
35+ assert(type(desc) == 'table' and integer(desc.pldm_type, 0, 63) and integer(desc.command, 0, 255))
36+ assert(integer(desc.version, 0, 0xffffffff) and type(desc.dispatch) == 'function')
37+ assert(integer(desc.min_req, 0, MAX_PLDM_REQ) and integer(desc.max_req, desc.min_req, MAX_PLDM_REQ))
38+ assert(integer(desc.max_rsp, 0, 4091) and integer(desc.timeout_ms, 1, 1000))
39+ local key = desc.pldm_type * 256 + desc.command
40+ assert(not self.handlers[key] and self.reg_count < 128, 'PLDM registration unavailable')
41+ for _, registered in pairs(self.handlers) do
42+ assert(
43+ registered.pldm_type ~= desc.pldm_type or registered.version == desc.version,
44+ 'PLDM type version mismatch'
45+ )
46+ end
47+ local handle, copy = {}, {}
48+ local fields = { 'pldm_type', 'command', 'version', 'dispatch', 'min_req', 'max_req', 'max_rsp', 'timeout_ms' }
49+ for _, field in ipairs(fields) do
50+ copy[field] = desc[field]
51+ end
52+ copy.handle = handle
53+ self.handlers[key], self.reg_count = copy, self.reg_count + 1
54+ return handle
55+end
56+ 
57+function responder:register_base(tid)
58+ return require('pldm_base').register(self, tid)
59+end
60+ 
61+function responder:remove(entry)
62+ if self.pending[entry.key] == entry then
63+ self.pending[entry.key], self.count = nil, self.count - 1
64+ end
65+end
66+ 
67+function responder:unregister(handle)
68+ for key, handler in pairs(self.handlers) do
69+ if handler.handle == handle then
70+ self.handlers[key], self.reg_count = nil, self.reg_count - 1
71+ for _, entry in pairs(self.pending) do
72+ if entry.handler == handler then
73+ self:remove(entry)
74+ end
75+ end
76+ return true
77+ end
78+ end
79+ return false
80+end
81+ 
82+function responder:reset()
83+ self.pending, self.count, self.generation = {}, 0, self.generation + 1
84+end
85+ 
86+function responder:expire()
87+ local now = self.now()
88+ for _, entry in pairs(self.pending) do
89+ if now >= entry.deadline then
90+ self:remove(entry)
91+ end
92+ end
93+end
94+ 
95+function responder:peer(source, address, expected)
96+ for _, ep in pairs(self.engine.endpoints.endpoints) do
97+ if
98+ ep.medium_type == def.PHY_MEDIUM_PCI
99+ and ep:get_msg_type() == MCTP_MESSAGE_TYPE_PLDM -- 其他消息类型的注册不能授予 PLDM 入站权限
100+ and ep.eid == source
101+ and ep.phy_addr == address
102+ and (not expected or expected == ep)
103+ then
104+ return ep
105+ end
106+ end
107+end
108+ 
109+function responder:valid(entry)
110+ local engine, physical, transport = self.engine, entry.physical, entry.transport
111+ return self.pending[entry.key] == entry
112+ and entry.generation == self.generation
113+ and self.now() < entry.deadline
114+ and engine.bmc_eid == transport.src
115+ and engine.bmc_phy_addr == physical.bmc_phy_addr
116+ and self:peer(transport.dest, physical.remote_phy_addr, entry.peer) ~= nil
117+end
118+ 
119+function responder:complete(entry, code, data)
120+ if not self:valid(entry) then
121+ self:remove(entry)
122+ return false
123+ end
124+ -- 一次领取先于发送,避免重入或迟到完成再次取得回复资格。
125+ self:remove(entry)
126+ if not integer(code, 0, 255) or type(data) ~= 'string' or #data > entry.max_rsp then
O
Oopenubmc-ci-bot13 days ago

💡 建议:complete() 对超长/非法响应未清除 pending 项,短时占用配额

pldm_responder.lua:122 当 #data > entry.max_rsp(或 code 非法)时直接 return false,并未 self:remove(entry)。该 entry 仍留在 self.pending 中且 self.count 不减,直到 deadline 过期后由 250ms 巡检清理。

由于 complete() 前面已通过 valid() 校验,这里的失败属于“一次性投递失败”;期间该 key 仍被 receive() 的 if self.pending[key] then return true 命中,同一来源/同一 Tag 的新请求会被当作重复请求静默丢弃。对 timeout_ms 较大(最多 1000ms)的处理命令,这个窗口最长可达约 1 秒,可能让对端新请求被错误拒收。

修复建议:在长度/类型校验失败返回 false 前也调用 self:remove(entry),保证失败即释放(校验失败后 dispatch 的 done 回调通常不会再被复用,当前测试 test_response_length_and_exception_cannot_leak_payload 先失败再成功是用例设计,建议同步斟酌该语义)。

likedislike
liuxingsen
13 days ago Comments:
127+ return false
128+ end
129+ entry.pldm.completion_code = code
130+ self.engine:pcie_binding_params_set()
131+ return self.engine.pldm_proto:push_checked(code == 0 and data or '', entry.pldm, entry.msg_key)
132+end
133+ 
134+function responder:receive(ctx, data, medium)
135+ local pldm = mctp.pldm_ctx.new()
136+ pldm:copy_from(ctx)
137+ if pldm.rq ~= 1 then
138+ return false
139+ end
140+ local transport = mctp.mctp_ctx.new()
141+ transport:copy_from(ctx)
142+ -- 不受理的请求直接消费,不能转入只处理响应的旧 requester。
143+ if medium ~= def.PHY_MEDIUM_PCI or not transport.tag_owner or pldm.d ~= 0 then
O
Oopenubmc-ci-bot13 days ago

💡 建议:Datagram / 非 PCIe / 非 owner 的 PLDM 请求被 return true 整体吞掉

当 responder 启用后,所有 rq=1 的 PLDM 请求即使不属于本 responder 的受理范围(Datagram、SMBus 介质、非 tag owner)也会 return true,直接消费掉而不再进入 mctp_engine:rx 的普通端点处理路径。

若产品后续需要同时存在其它 PLDM 服务/端点处理这些请求,这里可能会被提前短路。建议确认「受限 responder」语义,至少在注释里说明这些被吞请求不会转发到普通端点,避免后续维护者误判。

likedislike
liuxingsen
13 days ago Comments:
144+ return true
145+ end
146+ local engine, physical = self.engine, mctp.pcie_vdm_ctx.new()
147+ physical:copy_from(ctx)
atomgit-bot
atomgit-botatomgit-bot13 days ago

🔵 Low Priority

变更行(pldm_responder.lua:139-151,engine:rx 在 521-527 显式调用): function responder:receive(ctx, data, medium) local pldm, transport = mctp.pldm_ctx.new(), mctp.mctp_ctx.new() pldm:copy_from(ctx) transport:copy_from(ctx) if pldm.rq ~= 1 then return false end if medium ~= def.PHY_MEDIUM_PCI or not transport.tag_owner or pldm.d ~= 0 then return true end local engine, physical = self.engine, mctp.pcie_vdm_ctx.new() physical:copy_from(ctx) 影响:engine:rx 对所有 PLDM 报文(包括数量占多数的响应 rq==0,它们在第 143 行就返回 false 落回原有端点逻辑)都会先分配 2 个 Lua userdata 上下文并做 2 次 C++ 结构拷贝;只有真正需要应答的请求才需要这些对象。开启 responder 后这是每个 PLDM 报文的固定开销(第三对象 pcie_vdm_ctx 亦然),而 PLDM 查询在机房巡检/轮询场景是持续高频的。

修复方向:把 pldm/transport 的分配与 copy 移到 pldm.rq == 1 与 medium/tag_owner/d 判定之后(判定可先用 ctx.rq/ctx.d 读基础字段),或复用 responder 上的两个 scratch 上下文对象;真正需要跨异步回复保活的只有 entry 里的那份拷贝。

建议:把 pldm/mctp_ctx/pcie_vdm_ctx 的创建与 copy_from 下移到 rq==1 且 PCI/tag_owner/d==0 判定通过之后,或复用 responder 内的 scratch 上下文,避免每个 PLDM 响应也付出 2~3 次分配+拷贝。

likedislike
Incorrect?
liuxingsen
13 days ago Comments:
148+ local peer = self:peer(transport.src, physical.remote_phy_addr)
149+ if
150+ not integer(engine.bmc_eid, 8, 254)
151+ or transport.dest ~= engine.bmc_eid
152+ or physical.bmc_phy_addr ~= engine.bmc_phy_addr
153+ or not integer(transport.src, 8, 254)
154+ or not peer
155+ then
156+ return true
157+ end
158+ self:expire()
159+ local key = table.concat({ medium, transport.src, transport.dest, physical.remote_phy_addr, transport.tag }, ':')
160+ if self.pending[key] then
161+ return true
162+ end
163+ if self.count >= 64 then
164+ self.overload_drops = self.overload_drops + 1
165+ if self.overload_drops == 1 then
166+ log:debug('pldm_responder: capacity exhausted, src=%d tag=%d', transport.src, transport.tag)
167+ end
168+ return true
169+ end
170+ local handler = self.handlers[pldm.pldm_type * 256 + pldm.command_code]
171+ local code = 0
172+ if #data > MAX_PLDM_REQ then
173+ code = 3
174+ elseif not handler then
175+ code = 0x20
176+ for _, registered in pairs(self.handlers) do
177+ if registered.pldm_type == pldm.pldm_type then
178+ code = 5
179+ break
180+ end
181+ end
182+ elseif #data < handler.min_req or #data > handler.max_req then
183+ code = 3
184+ end
185+ transport.src, transport.dest = transport.dest, transport.src
186+ transport.tag_owner = false
187+ physical.routing = 2
188+ pldm.rq, pldm.d = 0, 0
189+ transport:set_prev(physical)
190+ pldm:set_prev(transport)
191+ -- 显式持有整条上下文链;C++ set_prev 只是裸指针,不保活 Lua 对象。
192+ local entry = {
193+ key = key,
194+ physical = physical,
195+ transport = transport,
196+ pldm = pldm,
197+ peer = peer,
198+ handler = handler,
199+ generation = self.generation,
200+ max_rsp = handler and handler.max_rsp or 0,
201+ deadline = self.now() + (handler and handler.timeout_ms or 50),
202+ msg_key = (1 << 16) | (transport.dest << 8) | medium,
203+ }
204+ self.pending[key], self.count = entry, self.count + 1
205+ if code ~= 0 then
206+ self:complete(entry, code, '')
207+ return true
208+ end
209+ local ok = pcall(handler.dispatch, data, function(cc, response)
210+ return self:complete(entry, cc, response)
211+ end)
212+ if not ok then
213+ log:error('pldm_responder: handler dispatch failed')
214+ self:complete(entry, 1, '')
215+ end
216+ return true
217+end
218+ 
219+return responder
@@ -31,5 +31,6 @@ require('test_routing_table')
31require('test_eid_table')31require('test_eid_table')
32require('test_mctp_dump')32require('test_mctp_dump')
33require('test_discovery')33require('test_discovery')
34+require('test_pldm_responder')
34 35 
35os.exit(lu.LuaUnit.run())36os.exit(lu.LuaUnit.run())
@@ -114,6 +114,7 @@ function TestMctpDiscovery:test_mctp_discovery_success()
114 end)114 end)
115 115 
116 self.mctp_discovery:smbus_set_eid_to_endpoint(116 self.mctp_discovery:smbus_set_eid_to_endpoint(
117+ 'discovery-success',
117 target_eid,118 target_eid,
118 ref_chip,119 ref_chip,
119 medium_type,120 medium_type,
@@ -158,6 +159,7 @@ function TestMctpDiscovery:test_mctp_discovery_failed()
158 local ref_chip = { path = path }159 local ref_chip = { path = path }
159 160 
160 self.mctp_discovery:smbus_set_eid_to_endpoint(161 self.mctp_discovery:smbus_set_eid_to_endpoint(
162+ 'discovery-failed',
161 target_eid,163 target_eid,
162 ref_chip,164 ref_chip,
163 medium_type,165 medium_type,
@@ -9,7 +9,7 @@
9-- See the Mulan PSL v2 for more details.9-- See the Mulan PSL v2 for more details.
10 10 
11local lu = require('luaunit')11local lu = require('luaunit')
12--- local mctp = require('mctp.core')12+local mctp = require('mctp.core')
13local mctp_def = require('mctp_def')13local mctp_def = require('mctp_def')
14local utils = require('mc.utils')14local utils = require('mc.utils')
15local c_mctp_engine = require('mctp_engine')15local c_mctp_engine = require('mctp_engine')
@@ -46,3 +46,89 @@ function TestEndpointPLDM:test_request_and_send()
46 ret, _ = pcall(self.send, self, req_bin, send_ctx)46 ret, _ = pcall(self.send, self, req_bin, send_ctx)
47 lu.assertEquals(ret, true)47 lu.assertEquals(ret, true)
48end48end
49+ 
50+local function pldm_peer(medium)
51+ local engine = c_mctp_engine.new(nil, 1)
52+ engine.bmc_eid, engine.smbus_bmc_eid = 11, 11
53+ local ep = c_pldm_endpoint.new(engine, 0x2000, 9, medium, mctp_def.ENDPOINT_IS_DISCOVERY)
54+ if medium == mctp_def.PHY_MEDIUM_SMBUS then
55+ ep.pldm_ctx = ep.smbus_pldm_ctx
56+ ep:smbus_ctx_set()
57+ end
58+ local ctx = mctp.mctp_ctx.new()
59+ ctx:set_prev(ep.ctx)
60+ ctx.src, ctx.dest, ctx.tag = 9, 11, 0
61+ local proto = mctp.pldm_protocol.new()
62+ local observations = { received = 0 }
63+ proto:set_rx_handler(function(incoming, data)
64+ observations.received = observations.received + 1
65+ return ep:rx(incoming, data)
66+ end)
67+ return engine, ep, ctx, proto, observations
68+end
69+ 
70+function TestEndpointPLDM:test_received_datagram_does_not_change_ordinary_request()
71+ for _, medium in ipairs({ mctp_def.PHY_MEDIUM_PCI, mctp_def.PHY_MEDIUM_SMBUS }) do
72+ for _, method in ipairs({ 'send', 'request' }) do
73+ local engine, ep, ctx, proto = pldm_peer(medium)
74+ ctx.tag_owner = true
75+ proto:pop('\x01\xc7\x03\x0c', ctx, medium)
76+ local sent
77+ -- 独立真实 codec 截取 PLDM 帧,避免 TX 继续进入物理介质封装。
78+ engine.pldm_proto = mctp.pldm_protocol.new()
79+ engine.pldm_proto:set_tx_handler(function(_, data)
80+ sent = data
81+ return true
82+ end)
83+ local req_ctx = { CommandCode = '5', PldmType = '0' }
84+ if method == 'send' then
85+ ep:send(nil, '\x03', req_ctx)
86+ else
87+ ep:request(nil, '\x03', 0, req_ctx, {})
88+ end
89+ lu.assertEquals(sent, '\x01\x81\x00\x05\x03')
90+ end
91+ end
92+end
93+ 
94+function TestEndpointPLDM:test_only_response_direction_can_complete_request()
95+ for _, medium in ipairs({ mctp_def.PHY_MEDIUM_PCI, mctp_def.PHY_MEDIUM_SMBUS }) do
96+ for _, case in ipairs({
97+ { owner = true, packet = '\x01\x81\x00\x05\x03', expected = 'waiting' },
98+ { owner = true, packet = '\x01\xc1\x00\x05\x03', expected = 'waiting' },
99+ { owner = true, packet = '\x01\x01\x00\x05\x00\x03', expected = 'waiting' },
100+ { owner = false, packet = '\x01\x81\x00\x05\x03', expected = 'waiting' },
101+ { owner = false, packet = '\x01\x01\x00\x05\x00\x03', expected = '\x03' },
102+ }) do
103+ local engine, ep, ctx, proto, observations = pldm_peer(medium)
104+ local key = ep:prepare_mctp_request(1)
105+ local pending = { co = coroutine.create(function() end), rsp = 'waiting' }
106+ engine.rsp_wait_list[key] = pending
107+ local transport = mctp.mctp_protocol.new()
108+ transport.pcie_eid, transport.smbus_eid = 11, 11
109+ transport:add_children(proto)
110+ local flags = 0xc0 | (case.owner and 8 or 0) | ctx.tag
111+ transport:pop(string.char(1, 11, 9, flags) .. case.packet, ep.ctx, medium)
112+ lu.assertEquals(observations.received, 1)
113+ lu.assertEquals(pending.rsp, case.expected)
114+ lu.assertIsNil(pending.err)
115+ lu.assertEquals(engine.rsp_wait_list[key], pending)
116+ end
117+ end
118+end
119+ 
120+function TestEndpointPLDM:test_invalid_request_fields_fail_before_waiting()
121+ for _, method in ipairs({ 'send', 'request' }) do
122+ for _, typ in ipairs({ '64', '-1', '0.5', 'bad' }) do
123+ local _, ep = pldm_peer(mctp_def.PHY_MEDIUM_PCI)
124+ local ctx = { PldmType = typ, CommandCode = '1' }
125+ local before = ep.req_total_count
126+ if method == 'send' then
127+ lu.assertError(ep.send, ep, nil, '', ctx)
128+ else
129+ lu.assertError(ep.request, ep, nil, '', 1, ctx, {})
130+ end
131+ lu.assertEquals(ep.req_total_count, before)
132+ end
133+ end
134+end
@@ -117,12 +117,121 @@ function TestMctpPldm:test_get_wwpn_rsp()
117 })117 })
118 self.mctp_proto.pcie_eid = 10118 self.mctp_proto.pcie_eid = 10
119 local data = self:pop(d, nil, mctp_def.PHY_MEDIUM_PCI)119 local data = self:pop(d, nil, mctp_def.PHY_MEDIUM_PCI)
120- if not data then120+ lu.assertNotNil(data)
121- return
122- end
123 local v = net_pldm_pkgs.wwpn_rsp(data)121 local v = net_pldm_pkgs.wwpn_rsp(data)
124 lu.assertNotNil(v)122 lu.assertNotNil(v)
125 lu.assertEquals(v.wwpn, '\x21\x00\xC4\x44\x7D\x06\xF2\x68')123 lu.assertEquals(v.wwpn, '\x21\x00\xC4\x44\x7D\x06\xF2\x68')
126 lu.assertEquals(v.manufacture_id, 0x07db)124 lu.assertEquals(v.manufacture_id, 0x07db)
127 lu.assertEquals(v.command_id, 0x02)125 lu.assertEquals(v.command_id, 0x02)
128end126end
127+ 
128+local function decode_pldm(packet)
129+ local proto = mctp.pldm_protocol.new()
130+ local received
131+ proto:set_rx_handler(function(ctx, data)
132+ received = { ctx = ctx:all(), data = data }
133+ return true
134+ end)
135+ proto:pop(packet, nil, mctp_def.PHY_MEDIUM_PCI)
136+ return received
137+end
138+ 
139+function TestMctpPldm:test_empty_request_is_not_a_short_response()
140+ local received = decode_pldm('\x01\x87\x03\x0c')
141+ lu.assertNotNil(received)
142+ lu.assertEquals(received.data, '')
143+ lu.assertEquals(received.ctx.rq, 1)
144+ lu.assertEquals(received.ctx.iid, 7)
145+ lu.assertEquals(received.ctx.pldm_type, 3)
146+ lu.assertEquals(received.ctx.command_code, 12)
147+end
148+ 
149+function TestMctpPldm:test_request_keeps_first_payload_byte()
150+ local received = decode_pldm('\x01\x8f\x00\x05\x03\xf0\xf0\xf1\x00')
151+ lu.assertNotNil(received)
152+ lu.assertEquals(received.data, '\x03\xf0\xf0\xf1\x00')
153+end
154+ 
155+function TestMctpPldm:test_datagram_flag_is_preserved()
156+ local received = decode_pldm('\x01\xc7\x03\x0c')
157+ lu.assertNotNil(received)
158+ lu.assertEquals(received.ctx.d, 1)
159+end
160+ 
161+function TestMctpPldm:test_short_error_response_keeps_completion_code()
162+ local received = decode_pldm('\x01\x07\x03\x0c\x8a')
163+ lu.assertNotNil(received)
164+ lu.assertEquals(received.ctx.completion_code, 0x8a)
165+ lu.assertEquals(received.data, '')
166+end
167+ 
168+function TestMctpPldm:test_reserved_bit_is_ignored_on_receive()
169+ -- DSP0240 1.1.0 §5.2:保留位发送为零、接收忽略。
170+ local received = decode_pldm('\x01\xa7\x03\x0c')
171+ lu.assertNotNil(received)
172+ lu.assertEquals(received.ctx.iid, 7)
173+ lu.assertEquals(received.data, '')
174+end
175+ 
176+function TestMctpPldm:test_invalid_headers_are_not_delivered()
177+ for _, packet in ipairs({
178+ '\x01\x87\x03', -- 缺少命令
179+ '\x01\x07\x03\x0c', -- 响应缺少完成码
180+ '\x01\x87\x43\x0c\x00', -- Hdr Ver 为类型字节高2位,0x43 表示版本1/类型3(DSP0240 表4)
181+ '\x01\x47\x03\x0c\x00', -- Datagram 不能用于响应
182+ '\x81\x87\x03\x0c\x00', -- 此路径未实现消息完整性校验
183+ }) do
184+ lu.assertNil(decode_pldm(packet))
185+ end
186+end
187+ 
188+function TestMctpPldm:test_response_encoding_includes_completion_code()
189+ local proto = mctp.pldm_protocol.new()
190+ local ctx = mctp.pldm_ctx.new()
191+ ctx.rq, ctx.iid, ctx.pldm_type, ctx.command_code = 0, 7, 3, 12
192+ ctx.completion_code = 0x8a
193+ local sent
194+ proto:set_tx_handler(function(_, data)
195+ sent = data
196+ return true
197+ end)
198+ proto:push('', ctx, mctp_def.PHY_MEDIUM_PCI)
199+ lu.assertEquals(sent, '\x01\x07\x03\x0c\x8a')
200+end
201+ 
202+function TestMctpPldm:test_datagram_encoding()
203+ local proto = mctp.pldm_protocol.new()
204+ local ctx = mctp.pldm_ctx.new()
205+ ctx.rq, ctx.d, ctx.iid, ctx.pldm_type, ctx.command_code = 1, 1, 7, 3, 12
206+ local sent
207+ proto:set_tx_handler(function(_, data)
208+ sent = data
209+ return true
210+ end)
211+ proto:push('\x03', ctx, mctp_def.PHY_MEDIUM_PCI)
212+ lu.assertEquals(sent, '\x01\xc7\x03\x0c\x03')
213+end
214+ 
215+function TestMctpPldm:test_invalid_context_never_transmits()
216+ for _, invalid in ipairs({
217+ { rq = 2 },
218+ { d = 2 },
219+ { rq = 0, d = 1 },
220+ { iid = 32 },
221+ { pldm_type = 64 },
222+ }) do
223+ local proto = mctp.pldm_protocol.new()
224+ local ctx = mctp.pldm_ctx.new()
225+ ctx.rq = 1
226+ for field, value in pairs(invalid) do
227+ ctx[field] = value
228+ end
229+ local sent = false
230+ proto:set_tx_handler(function()
231+ sent = true
232+ return true
233+ end)
234+ proto:push('', ctx, mctp_def.PHY_MEDIUM_PCI)
235+ lu.assertFalse(sent)
236+ end
237+end
@@ -0,0 +1,366 @@
1+-- Copyright (c) 2026 Huawei Technologies Co., Ltd.
2+-- openUBMC is licensed under Mulan PSL v2.
3+-- You can use this software according to the terms and conditions of the Mulan PSL v2.
4+-- You may obtain a copy of Mulan PSL v2 at:
5+-- http://license.coscl.org.cn/MulanPSL2
6+-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
7+-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
8+-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
9+-- See the Mulan PSL v2 for more details.
10+ 
11+-- SPDX-License-Identifier: MulanPSL-2.0
12+local lu = require('luaunit')
13+local mctp = require('mctp')
14+local Engine = require('mctp_engine')
15+local def = require('mctp_def')
16+local timer = require('skynet').timer
17+ 
18+TestPldmResponder = {}
19+ 
20+function TestPldmResponder:setup()
21+ self.original_timer, self.timers = self.original_timer or timer.new, {}
22+ self.engine = Engine.new(nil, 1)
23+ self.engine:init()
24+ self.engine.endpoints:init()
25+ self.engine:set_bmc_eid(11, 0x300)
26+ -- ep_mctp 构造时 htons(0x20)=0x2000;收包上下文使用转换后的地址。
27+ self.peer = self.engine.endpoints:create_endpoint(0x20, 9, 1, def.PHY_MEDIUM_PCI)
28+ lu.assertEquals(self.peer.phy_addr, 0x2000)
29+ self.frames, self.done = {}, {}
30+ self.engine.hw_pcie_proto:set_tx_handler(function()
31+ return true
32+ end)
33+ self.engine.pcie_proto:set_capture_handler(function(is_push, _, data)
34+ if is_push then
35+ self.frames[#self.frames + 1] = data
36+ end
37+ end)
38+ self.physical = mctp.pcie_vdm_ctx.new()
39+ self.physical.remote_phy_addr, self.physical.bmc_phy_addr = 0x2000, 0x300
atomgit-bot
atomgit-botatomgit-bot13 days ago

🔵 Low Priority

改动行:test/unit/test_pldm_responder.lua:30 用 self.engine.endpoints:create_endpoint(0x20, 9, 2, def.PHY_MEDIUM_PCI) 创建已被登记的对端,而第 41 行构造入向上下文时用的是 self.physical.remote_phy_addr = 0x2000;142/161 行又把「地址变化」表达成 0x2100。

已知同一仓库的调用约定(test/unit/test_libmctp/test_mctp_engine.lua:149-152)是 create_endpoint(phy_addr, eid, msg_type),即第一个参数就是完整 phy_addr(那里传 1688 并且注释明确说「测试数据的物理地址是 1688」)。按此约定,对端的 phy_addr 是 0x20,而所有入向路由宣称的远端地址是 0x2000,两者并不相等。

失败模式:两条互斥的断言只能有一条成立——若 responder 在收包时用「入向 remote_phy_addr == 端点 phy_addr」做校验,则 test_expiry_unregister_and_address_changes_cancel_reply:132 的 baseline(#self.done == 1)会被拒绝而失败;若不校验,则 test_wrong_direction_datagram_and_unknown_source_are_not_answered:161-165 的 0x2100 用例会被正常受理而使 #self.done == 0 失败。两者都通过的唯一解释是 0x20 被隐式左移 8 位当作总线号(0x20<<8 == 0x2000),这从未在用例里写明;若无此隐式转换,地址撤销/地址变更这两组断言就是「因错误原因通过」或干脆未覆盖目标行为。

建议:统一对端端点 phy_addr 与入向上下文 remote_phy_addr 的取值(都用 0x2000,或显式注明总线号到 phy_addr 的换算),使「地址变更撤销回复」的断言确实由 0x2000→0x2100 的地址变化驱动。

likedislike
Incorrect?
liuxingsen
13 days ago Comments:
40+ self.incoming = mctp.mctp_ctx.new()
41+ self.incoming:set_prev(self.physical)
42+ self.incoming.src, self.incoming.dest = 9, 11
43+ self.incoming.tag_owner, self.incoming.tag = true, 1
44+ timer.new = function(delay, fn)
45+ self.timers[#self.timers + 1] = fn
46+ return { stop = function() end }
47+ end
48+end
49+ 
50+function TestPldmResponder:teardown()
51+ timer.new = self.original_timer
52+ self.original_timer = nil
53+end
54+ 
55+function TestPldmResponder:test_idle_expiry_and_old_configuration_callback_are_isolated()
56+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 1 })
57+ local old, calls = self.engine.pldm_responder, 0
58+ old.expire = function()
59+ calls = calls + 1
60+ end
61+ self.timers[1]()
62+ lu.assertEquals(calls, 1)
63+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 1 })
64+ self.timers[2]()
65+ lu.assertEquals(calls, 1)
66+ lu.assertEquals(#self.timers, 3)
atomgit-bot
atomgit-botatomgit-bot13 days ago

🔵 Low Priority

改动行:test/unit/test_pldm_responder.lua:65 先调用 self.timers3,第 66 行才断言 #self.timers == 3。

问题一(顺序):若 configure_pldm_responder({CONFIG_MCTP_EP_RESPONDER=false}) 不新建定时器(关闭时只 stop 已有定时器是最自然的实现),第 65 行会以 "attempt to call a nil value" 直接抛错,断言消息完全无法指向真实原因;应先断言数量再解引用。

问题二(断言空转):关闭分支里 timers3 之后没有任何断言:既没有复核 calls 仍为 1(旧回调未被驱动),也没有断言引擎已不再应答(例如再发一次 GetTID 不应产生 frame),只断言了「定时器数量是 3」。同理第 62-63 行只证明了旧 expire 没被调用,却没有证明 timers[2] 触发的是新 responder 的 expire —— 如果重新配置后定时器回调什么都没驱动(死定时器),本用例依旧全绿。

失败模式:闲置回收/配置重绑定的回归(重新配置后巡检不再驱动新 responder,或关闭后旧定时器仍在跑)无法被本用例捕获,与用例名称所声称的隔离能力不符。

建议:把数量断言放到调用之前,并在触发 timers[3] 后复核旧回调未被再次调用、且关闭后引擎不再产生应答帧。

likedislike
Incorrect?
liuxingsen
13 days ago Comments:
67+ local fresh = 0
68+ self.engine.pldm_responder.expire = function()
69+ fresh = fresh + 1
70+ end
71+ self.timers[3]()
72+ lu.assertEquals(fresh, 1)
73+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = false })
74+ lu.assertEquals(#self.timers, 4)
75+ self.timers[4]()
76+ lu.assertEquals({ calls, fresh, #self.timers }, { 1, 1, 4 })
77+ self:receive('\x01\x87\x00\x02')
78+ lu.assertEquals(#self.frames, 0)
79+end
80+ 
81+function TestPldmResponder:test_ncsi_registration_does_not_authorize_public_pldm_commands()
82+ self.engine.endpoints.endpoints = {}
83+ self.engine.endpoints:create_endpoint(0x20, 9, 2, def.PHY_MEDIUM_PCI)
84+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 17 })
85+ self:receive('\x01\x87\x00\x02')
86+ lu.assertEquals(#self.frames, 0)
87+ self.engine.endpoints:create_endpoint(0x20, 9, 1, def.PHY_MEDIUM_PCI)
88+ self:receive('\x01\x87\x00\x02')
89+ lu.assertEquals(#self.frames, 1)
90+ lu.assertEquals(self.frames[1]:sub(13, 22), '\x01\x09\x0b\xc1\x01\x07\x00\x02\x00\x11')
91+end
92+ 
93+function TestPldmResponder:test_public_reply_is_cancelled_when_only_ncsi_endpoint_remains()
94+ self:enable()
95+ self:receive()
96+ lu.assertEquals(#self.done, 1)
97+ self.engine.endpoints.endpoints = {}
98+ self.engine.endpoints:create_endpoint(0x20, 9, 2, def.PHY_MEDIUM_PCI)
99+ lu.assertFalse(self.done[1](0, 'late'))
100+ lu.assertEquals(#self.frames, 0)
101+end
102+ 
103+function TestPldmResponder:test_invalid_product_tid_keeps_responder_disabled()
104+ for _, tid in ipairs({ false, '1', 0, 255, 1.5 }) do
105+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = tid })
106+ lu.assertIsNil(self.engine.pldm_responder)
107+ lu.assertIsNil(self.engine.pldm_reply_timer)
108+ end
109+ local saved = package.loaded.pldm_time
110+ package.loaded.pldm_time = {
111+ register = function()
112+ error('time adapter unavailable')
113+ end,
114+ }
115+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 1, CONFIG_MCTP_PLDM_TIME = true })
116+ package.loaded.pldm_time = saved
117+ lu.assertIsNil(self.engine.pldm_responder)
118+ lu.assertIsNil(self.engine.pldm_reply_timer)
119+end
120+ 
121+function TestPldmResponder:test_registered_request_uses_original_route_once()
122+ self:enable()
123+ self.engine.pldm_proto:pop('\x01\x87\x03\x0c', self.incoming, def.PHY_MEDIUM_PCI)
124+ self.incoming.tag = 6
125+ self.engine.pldm_proto:pop('\x01\x89\x03\x0c', self.incoming, def.PHY_MEDIUM_PCI)
126+ lu.assertEquals(#self.done, 2)
127+ self.incoming, self.physical = nil, nil
128+ collectgarbage('collect')
129+ -- 后到请求先完成,原请求仍必须保留 IID=7 / Tag=1。
130+ self.done[2](0, 'second')
131+ self.done[1](0, 'first')
132+ lu.assertEquals(#self.frames, 2)
133+ lu.assertEquals(self.frames[1]:sub(13, 21), '\x01\x09\x0b\xc6\x01\x09\x03\x0c\x00')
134+ lu.assertEquals(self.frames[2]:sub(13, 21), '\x01\x09\x0b\xc1\x01\x07\x03\x0c\x00')
135+ lu.assertEquals(self.frames[2]:sub(22, 26), 'first')
136+ lu.assertFalse(self.done[1](0, 'again'))
137+ lu.assertEquals(#self.frames, 2)
138+end
139+ 
140+function TestPldmResponder:descriptor(pldm_type, command, version, max_rsp, dispatch)
141+ return {
142+ pldm_type = pldm_type,
143+ command = command,
144+ version = version,
145+ min_req = 0,
146+ max_req = 0,
147+ max_rsp = max_rsp,
148+ timeout_ms = 50,
149+ dispatch = dispatch,
150+ }
151+end
152+ 
153+function TestPldmResponder:enable()
154+ local responder = self.engine:enable_pldm_responder()
155+ self.now = 100
156+ responder.now = function()
157+ return self.now
158+ end
159+ local registration = responder:register(self:descriptor(3, 12, 0xF1F0F000, 7, function(_, complete)
160+ self.done[#self.done + 1] = complete
161+ end))
162+ return responder, registration
163+end
164+ 
165+function TestPldmResponder:receive(packet)
166+ self.engine.pldm_proto:pop(packet or '\x01\x87\x03\x0c', self.incoming, def.PHY_MEDIUM_PCI)
167+end
168+ 
169+function TestPldmResponder:test_expiry_unregister_and_address_changes_cancel_reply()
170+ for _, action in ipairs({ 'expiry', 'unregister', 'address', 'reinit', 'peer' }) do
171+ self:setup()
172+ local responder, registration = self:enable()
173+ self:receive()
174+ lu.assertEquals(#self.done, 1)
175+ if action == 'expiry' then
176+ self.now = 150
177+ elseif action == 'unregister' then
178+ responder:unregister(registration)
179+ elseif action == 'address' then
180+ self.engine:set_bmc_eid(12, 0x300)
181+ elseif action == 'reinit' then
182+ self.engine:reinit()
183+ else
184+ self.peer.phy_addr = 0x2100
185+ end
186+ lu.assertFalse(self.done[1](0, 'late'))
187+ lu.assertEquals(#self.frames, 0)
188+ lu.assertEquals(responder.count, 0)
189+ end
190+end
191+ 
192+function TestPldmResponder:test_wrong_direction_datagram_and_unknown_source_are_not_answered()
193+ self:enable()
194+ self:receive('\x01\xc7\x03\x0c')
195+ self.incoming.tag_owner = false
196+ self:receive()
197+ self.incoming.tag_owner = true
198+ self.incoming.src = 10
199+ self:receive()
200+ self.incoming.src, self.incoming.dest = 9, 12
201+ self:receive()
202+ self.incoming.dest = 11
203+ self.physical.remote_phy_addr = 0x2100
204+ self:receive()
205+ self.physical.remote_phy_addr, self.physical.bmc_phy_addr = 0x2000, 0x400
206+ self:receive()
207+ lu.assertEquals(#self.done, 0)
208+ lu.assertEquals(#self.frames, 0)
209+end
210+ 
211+function TestPldmResponder:test_duplicate_tag_does_not_replace_live_request()
212+ local responder = self:enable()
213+ for tag = 0, 7 do
214+ self.incoming.tag = tag
215+ self:receive()
216+ end
217+ self:receive('\x01\x88\x03\x0c')
218+ lu.assertEquals(#self.done, 8)
219+ lu.assertEquals(responder.count, 8)
220+ self.now = 150
221+ responder:expire()
222+ lu.assertEquals(responder.count, 0)
223+ self:receive()
224+ lu.assertEquals(#self.done, 9)
225+ lu.assertFalse(self.done[8](0, 'old'))
226+ lu.assertEquals(responder.count, 1)
227+ lu.assertTrue(self.done[9](0, 'new'))
228+end
229+ 
230+function TestPldmResponder:test_length_error_unknown_command_and_type_reply_with_completion_only()
231+ self:enable()
232+ for _, case in ipairs({
233+ { '\x01\x87\x03\x0c\x00', 3 },
234+ { '\x01\x87\x03\x0c' .. string.rep('x', 4093), 3 },
235+ { '\x01\x87\x03\xff', 5 },
236+ { '\x01\x87\x3f\x0c', 0x20 },
237+ }) do
238+ self:receive(case[1])
239+ lu.assertEquals(self.frames[#self.frames]:byte(21), case[2])
240+ end
241+ lu.assertEquals(#self.frames, 4)
242+ lu.assertEquals(#self.done, 0)
243+end
244+ 
245+function TestPldmResponder:test_response_length_and_exception_cannot_leak_payload()
246+ local responder = self:enable()
247+ self:receive()
248+ lu.assertFalse(self.done[1](0, string.rep('x', 8)))
249+ lu.assertEquals(#self.frames, 0)
250+ lu.assertFalse(self.done[1](0, 'late'))
251+ lu.assertEquals(responder.count, 0)
252+ self:receive()
253+ lu.assertTrue(self.done[2](0, 'ok'))
254+ responder:register(self:descriptor(3, 13, 0xF1F0F000, 0, function()
255+ error('private service detail')
256+ end))
257+ self:receive('\x01\x87\x03\x0d')
258+ lu.assertEquals(self.frames[2]:byte(21), 1)
259+ lu.assertEquals(responder.count, 0)
260+end
261+ 
262+function TestPldmResponder:test_base_tid_and_capabilities_follow_registration()
263+ local responder, registration = self:enable()
264+ responder:register_base(42)
265+ self:receive('\x01\x87\x00\x02')
266+ lu.assertEquals(self.frames[1]:sub(21, 22), '\x00\x2a')
267+ self:receive('\x01\x87\x00\x04')
268+ lu.assertEquals(self.frames[2]:sub(21, 29), '\x00\x09' .. string.rep('\x00', 7))
269+ self:receive('\x01\x87\x00\x05\x00\x00\xf0\xf1\xf1')
270+ lu.assertEquals(self.frames[3]:sub(21, 53), '\x00\x3c' .. string.rep('\x00', 31))
271+ responder:unregister(registration)
272+ self:receive('\x01\x87\x00\x04')
273+ lu.assertEquals(self.frames[4]:sub(21, 29), '\x00\x01' .. string.rep('\x00', 7))
274+ self.engine:set_bmc_eid(12, 0x300)
275+ self.incoming.dest = 12
276+ self:receive('\x01\x87\x00\x02')
277+ lu.assertEquals(self.frames[5]:sub(21, 22), '\x00\x2a')
278+end
279+ 
280+function TestPldmResponder:test_base_version_ignores_first_handle_and_validates_next()
281+ local responder = self:enable()
282+ responder:register_base(42)
283+ -- DSP0240:GetFirstPart 必须忽略 DataTransferHandle。
284+ self:receive('\x01\x87\x00\x03\xff\xff\xff\xff\x01\x00')
285+ lu.assertEquals(self.frames[1]:sub(21, 30), '\x00\x00\x00\x00\x00\x05\x00\xf0\xf1\xf1')
286+ lu.assertEquals(self.frames[1]:sub(31, 34), '\xba\xbe\x9d\x53')
287+ self:receive('\x01\x87\x00\x03\x00\x00\x00\x00\x01\x03')
288+ local version = self.frames[2]:sub(27, 30)
289+ lu.assertEquals(version, '\x00\xf0\xf0\xf1')
290+ self:receive('\x01\x87\x00\x05\x03' .. version)
291+ lu.assertEquals(self.frames[3]:sub(21, 53), '\x00\x00\x10' .. string.rep('\x00', 30))
292+ for _, case in ipairs({
293+ { '\x03\x00\x00\x00\x00\x00\x00', 0x80 },
294+ { '\x03\x00\x00\x00\x00\x02\x00', 0x81 },
295+ { '\x03\x00\x00\x00\x00\x01\x3f', 0x83 },
296+ { '\x05\x3f\x00\xf0\xf0\xf1', 0x83 },
297+ { '\x05\x03\x01\xf0\xf0\xf1', 0x84 },
298+ }) do
299+ self:receive('\x01\x87\x00' .. case[1])
300+ lu.assertEquals(self.frames[#self.frames]:byte(21), case[2])
301+ end
atomgit-bot
atomgit-botatomgit-bot13 days ago

🟡 Medium Priority

改动行:test/unit/test_pldm_responder.lua:251 断言 GetPLDMVersion(类型 3) 返回的版本字节为 00 F0 F1 F1(即 0xF1F1F000);而 enable() 里所有注册项的 version 都是 0xF1F0F000(00 F0 F0 F1)。253-262 行对 GetPLDMCommands 只构造了 5 条失败用例(0x83/0x84),没有任何一条成功用例。

受影响的契约:DSP0240 Base 命令协商链路 —— 主机先用 GetPLDMVersion 取得该类型支持的版本,再用「同一版本」去调 GetPLDMCommands。测试集里该版本值来自注册项的 0xF1F0F000,而 GetPLDMVersion 对外宣告的是 0xF1F1F000,两者不同。

失败模式:实现若按注册项版本(0xF1F0F000)校验,则守规主机用 251 行宣告的 0xF1F1F000 请求 GetPLDMCommands 会收到 0x84(版本无效);反之若按宣告版本校验,则用注册版本请求会收到 0x84。由于 253-262 只有失败用例、且错误码 0x83/0x84 与「版本恰好写错」无法区分,这条互操作缺陷(真实主机拿不到命令列表)不会被任何用例捕获。注意用例 4('\x05\x3f...')因类型错误先返回 0x83,也未覆盖成功路径。

建议:新增 GetPLDMCommands 成功用例(pldm_type=3,版本与 GetPLDMVersion 返回的 0xF1F1F000 一致),断言完成码 0x00 及返回的命令位图;并核对 GetPLDMVersion 宣告的版本与注册项 version 是否应一致,避免守规主机拿到版本“无效”错误码。

likedislike
Incorrect?
liuxingsen
13 days ago Comments:
302+end
303+ 
304+function TestPldmResponder:test_peer_replacement_cannot_receive_old_reply()
305+ self:enable()
306+ self:receive()
307+ self.engine.endpoints:reset()
308+ self.engine.endpoints:create_endpoint(0x20, 9, 1, def.PHY_MEDIUM_PCI)
309+ lu.assertFalse(self.done[1](0, 'old'))
310+ lu.assertEquals(#self.frames, 0)
311+end
312+ 
313+function TestPldmResponder:test_global_quota_and_live_entries_are_preserved()
314+ local responder = self:enable()
315+ for source = 9, 17 do
316+ self.engine.endpoints:create_endpoint(0x20, source, 1, def.PHY_MEDIUM_PCI)
317+ self.incoming.src = source
318+ for tag = 0, 7 do
319+ self.incoming.tag = tag
320+ self:receive()
321+ end
322+ end
323+ lu.assertEquals({ #self.done, responder.count, responder.overload_drops, #self.frames }, { 64, 64, 8, 0 })
324+ lu.assertTrue(self.done[1](0, 'first'))
325+ self:receive()
326+ lu.assertEquals(#self.done, 65)
327+ lu.assertEquals(responder.count, 64)
328+end
329+ 
330+function TestPldmResponder:test_registration_copy_duplicate_version_and_limits()
331+ local responder = self:enable()
332+ local desc = self:descriptor(2, 0, 0xF1F0F000, 1, function(_, done)
333+ done(0, 'a')
334+ end)
335+ responder:register(desc)
336+ desc.dispatch = function()
337+ error('must not replace handler')
338+ end
339+ self:receive('\x01\x87\x02\x00')
340+ lu.assertEquals(self.frames[1]:sub(21, 22), '\x00a')
341+ lu.assertError(responder.register, responder, desc)
342+ desc.command, desc.version = 1, 0xF1F1F000
343+ lu.assertError(responder.register, responder, desc)
344+ desc.version = 0xF1F0F000
345+ for command = 1, 126 do
346+ desc.command = command
347+ responder:register(desc)
348+ end
349+ lu.assertEquals(responder.reg_count, 128)
350+ desc.command = 127
351+ lu.assertError(responder.register, responder, desc)
352+end
353+ 
354+function TestPldmResponder:test_product_configuration_controls_advertised_capabilities()
355+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = false })
356+ self:receive('\x01\x87\x00\x02')
357+ lu.assertEquals(#self.frames, 0)
358+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = true, PLDM_TID = 43 })
359+ self:receive('\x01\x87\x00\x02')
360+ lu.assertEquals(self.frames[1]:sub(21, 22), '\x00\x2b')
361+ self:receive('\x01\x87\x00\x04')
362+ lu.assertEquals(self.frames[2]:sub(21, 29), '\x00\x01' .. string.rep('\x00', 7))
363+ self.engine:configure_pldm_responder({ CONFIG_MCTP_EP_RESPONDER = false })
364+ self:receive('\x01\x87\x00\x02')
365+ lu.assertEquals(#self.frames, 2)
366+end