Open
fix: 路由重绑定向撤销旧事务并恢复端点 #15
liuxingsenAdd on 13 days ago
fix: 路由重绑定向撤销旧事务并恢复端点 #15
Open
Merged fromxiaoxinglxs/mctpdfix/mctp-peer-rebindintoopenUBMC/mctpdmain
Total: 16 files changed+841-40
| @@ -1,5 +1,8 @@ | |||
| 1 | # Changelog | 1 | # Changelog |
| 2 | All notable changes to this project will be documented in this file. | 2 | All notable changes to this project will be documented in this file. |
| 3 | +## [1.130.20] - 2026-10-27 | ||
| 4 | +路由变化定向撤销旧端点和事务,修复两卡交换EID的登记串改、驱动失败后的恢复、MDB订阅及物理迁移;与共享传输/回复上下文/有界发送配套。 | ||
| 5 | + | ||
| 3 | ## [1.130.15] - 2026-08-25 | 6 | ## [1.130.15] - 2026-08-25 |
| 4 | 更新版本号,触发流水线推包 | 7 | 更新版本号,触发流水线推包 |
| 5 | 8 | ||
| @@ -0,0 +1,15 @@ | |||
| 1 | +# MCTP路由与订阅重绑 | ||
| 2 | + | ||
| 3 | +当同一物理地址的EID、范围、介质或其他路由字段变化时,原路由表只比较物理地址,未发布更新。现在比较完整平坦字段,先删除旧路由再添加新路由;同值轮询保持幂等,路由删除同步移除MDB对象缓存。 | ||
| 4 | + | ||
| 5 | +`upd_mdb_endpoint`原来给四参数订阅函数传三个错位参数,并无条件调用尚未建立的signal。现在传递system_id、engine、MDB对象和app,允许初始订阅尚未完成;更新ep_info,使trace定位新的EID。旧订阅断开后绑定新端点;延迟执行的初始订阅不能再次绑定,避免一条通知重复投递。删除时标记失效、断开已存在订阅并清理诊断映射。 | ||
| 6 | + | ||
| 7 | +路由撤销先退休对应端点,终结查询等待、重组、响应预约和PLDM回复上下文,再恢复新路由的各协议登记;共享发送队列用端点对象身份拒绝旧消息。物理地址迁移同步重建MDB路径。驱动配置失败时标记未就绪,随已有路由轮询重试,不保留指向旧地址的订阅。PCIe路由删除/添加采用相同绑定过滤,非PCIe同地址删除不撤销PCIe端点;退休对象的新请求/发送显式抛出MctpEndpointReset。优先匹配物理地址,避免两卡交换EID时旧EID抢先命中另一张卡;协议类型属于登记键,类型变化须独立登记。 | ||
| 8 | + | ||
| 9 | +验证覆盖完整字段变化与同值独立对象重放、两卡交换EID的两种路由顺序、初次失败后改EID恢复、多个协议消费者逐个重绑、物理迁移、新旧通知归属、延迟订阅和重复删除。保留两处discovery测试漏传ep_name的修正。 | ||
| 10 | + | ||
| 11 | +43000首次真实换址暴露接收路径只按EID查端点,旧物理来源通知会误投给新持有者;已增加PCIe物理地址校验及先失败后通过的回归。正常来源仍保留既有未匹配响应通知语义;配套 qemu_mockup !190 修正NC-SI模板的完整BDF后,43000集成1ba53ad已实测旧来源零误投递、重绑/恢复8条通知正确归属;变化网卡查询3.68秒失效,未变化网卡保持15.01秒正常超时。未注入查询响应。 | ||
| 12 | + | ||
| 13 | +边界:早期独立!15只含路由/MDB修复;本轮候选增加上层peer失效接线,组合依赖!11的协议隔离、!13的回复上下文和!14的有界发送。上述实测对应1ba53ad集成候选;后续非PCIe删除守卫、退休错误分类及测试隔离变更已单独回归,不冒充同次实机测试。已经写入硬件的报文不能撤回,过期后的任意迟到无法仅靠有限Tag区分;完整SMBus接口身份、本端整体复位的旧Tag隔离、Control/V2和阶段2仍待完成。版本保持1.130.20,按用户要求暂不合入。 | ||
| 14 | + | ||
| 15 | +注册表的add_reg_info固定使用PCIe介质;SMBus发现直接管理自己的端点,不进入该表。PCIe登记更新/删除和路由刷新只恢复对应PCIe身份,不宣称完成跨SMBus接口生命周期支持。新增同地址/EID的SMBus端点回归,确认PCIe登记变更及清表不会撤销它。 | ||
| @@ -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.20", |
| 6 | "author": "Huawei", | 6 | "author": "Huawei", |
| 7 | "license": "Mulan PSL v2", | 7 | "license": "Mulan PSL v2", |
| 8 | "options": { | 8 | "options": { |
| @@ -160,8 +160,8 @@ function mctpd_app:subscribe_changed_msg(system_id) | |||
| 160 | self.mctp_engines[system_id]:get_eid_table().on_add_reg:on(function(reg) | 160 | self.mctp_engines[system_id]:get_eid_table().on_add_reg:on(function(reg) |
| 161 | self.mctp_mdb_mgmts[system_id]:add_mdb_endpoint(reg) | 161 | self.mctp_mdb_mgmts[system_id]:add_mdb_endpoint(reg) |
| 162 | end) | 162 | end) |
| 163 | - self.mctp_engines[system_id]:get_eid_table().on_update_reg:on(function(reg) | 163 | + self.mctp_engines[system_id]:get_eid_table().on_update_reg:on(function(reg, _, previous) |
| 164 | - self.mctp_mdb_mgmts[system_id]:upd_mdb_endpoint(reg) | 164 | + self.mctp_mdb_mgmts[system_id]:upd_mdb_endpoint(reg, previous) |
| 165 | end) | 165 | end) |
| 166 | self.mctp_engines[system_id]:get_eid_table().on_del_reg:on(function(reg) | 166 | self.mctp_engines[system_id]:get_eid_table().on_del_reg:on(function(reg) |
| 167 | self.mctp_mdb_mgmts[system_id]:del_mdb_endpoint(reg) | 167 | self.mctp_mdb_mgmts[system_id]:del_mdb_endpoint(reg) |
| @@ -29,29 +29,41 @@ function c_mctp_eid_table.new(system_id) | |||
| 29 | }, c_mctp_eid_table) | 29 | }, c_mctp_eid_table) |
| 30 | end | 30 | end |
| 31 | 31 | ||
| 32 | ----查找注册表,找到第一个 phy_addr 或 eid 匹配的条目 | 32 | +---先匹配物理地址;只有没有该物理登记时才按EID查找迁移项。协议类型是登记键。 |
| 33 | function c_mctp_eid_table:find_reg_info(pid, msg_src_eid, phy_addr, msg_type) | 33 | function c_mctp_eid_table:find_reg_info(pid, msg_src_eid, phy_addr, msg_type) |
| 34 | + local eid_match | ||
| 34 | for _, reg in ipairs(self.reg_table) do | 35 | for _, reg in ipairs(self.reg_table) do |
| 35 | - if | 36 | + if reg.pid == pid and msg_type == reg.msg_type then |
| 36 | - reg.pid == pid | 37 | + if reg.phy_addr == phy_addr then |
| 37 | - and msg_type == reg.msg_type | 38 | + return reg |
| 38 | - and (reg.phy_addr == phy_addr or reg.msg_src_eid == msg_src_eid) | 39 | + end |
| 39 | - then | 40 | + if not eid_match and reg.msg_src_eid == msg_src_eid then |
| 40 | - return reg | 41 | + eid_match = reg |
| 42 | + end | ||
| 41 | end | 43 | end |
| 42 | end | 44 | end |
| 43 | - | 45 | + return eid_match |
| 44 | - return nil | ||
| 45 | end | 46 | end |
| 46 | 47 | ||
| 47 | function c_mctp_eid_table:add_reg_info(process_tag, pid, msg_src_eid, phy_addr, msg_type, module_name) | 48 | function c_mctp_eid_table:add_reg_info(process_tag, pid, msg_src_eid, phy_addr, msg_type, module_name) |
| 48 | local reg = self:find_reg_info(pid, msg_src_eid, phy_addr, msg_type) | 49 | local reg = self:find_reg_info(pid, msg_src_eid, phy_addr, msg_type) |
| 49 | if reg then | 50 | if reg then |
| 50 | - local need_re_config = (reg.phy_addr ~= phy_addr or reg.msg_src_eid ~= msg_src_eid) | 51 | + local previous = {} |
| 52 | + for key, value in pairs(reg) do | ||
| 53 | + previous[key] = value | ||
| 54 | + end | ||
| 55 | + local need_re_config = ( | ||
| 56 | + reg.phy_addr ~= phy_addr | ||
| 57 | + or reg.msg_src_eid ~= msg_src_eid | ||
| 58 | + or reg.route_withdrawn == true | ||
| 59 | + or reg.config_failed == true | ||
| 60 | + ) | ||
| 51 | local need_update = (need_re_config or (reg.module_name ~= module_name)) | 61 | local need_update = (need_re_config or (reg.module_name ~= module_name)) |
| 52 | if need_re_config then | 62 | if need_re_config then |
| 53 | reg.phy_addr = phy_addr | 63 | reg.phy_addr = phy_addr |
| 54 | reg.msg_src_eid = msg_src_eid | 64 | reg.msg_src_eid = msg_src_eid |
| 65 | + reg.route_withdrawn = nil | ||
| 66 | + reg.ep_status = mctp_def.ENDPOINT_IS_DISCOVERY | ||
| 55 | end | 67 | end |
| 56 | if need_update then | 68 | if need_update then |
| 57 | reg.module_name = module_name | 69 | reg.module_name = module_name |
| @@ -63,7 +75,7 @@ function c_mctp_eid_table:add_reg_info(process_tag, pid, msg_src_eid, phy_addr, | |||
| 63 | phy_addr, | 75 | phy_addr, |
| 64 | msg_type | 76 | msg_type |
| 65 | ) | 77 | ) |
| 66 | - self.on_update_reg:emit(reg, need_re_config) | 78 | + self.on_update_reg:emit(reg, need_re_config, previous) |
| 67 | end | 79 | end |
| 68 | return true | 80 | return true |
| 69 | end | 81 | end |
| @@ -13,6 +13,7 @@ local mctp = require('mctp') | |||
| 13 | local network = require('network.core') | 13 | local network = require('network.core') |
| 14 | local signal = require('mc.signal') | 14 | local signal = require('mc.signal') |
| 15 | local mctp_def = require('mctp_def') | 15 | local mctp_def = require('mctp_def') |
| 16 | +local errors = require('mctpd.errors') | ||
| 16 | 17 | ||
| 17 | local MSG_TAG_MASK <const> = 0x07 | 18 | local MSG_TAG_MASK <const> = 0x07 |
| 18 | local ROUTE_BY_ID <const> = 0x02 | 19 | local ROUTE_BY_ID <const> = 0x02 |
| @@ -94,7 +95,7 @@ function c_ep_mctp:request(id, cb, timeout_ms, more_process_cb) | |||
| 94 | local key = self:prepare_mctp_request(id) | 95 | local key = self:prepare_mctp_request(id) |
| 95 | -- 暂存本次请求的rsp key,smbus发送时随请求带给service,失败回传后用于完整匹配唤醒 | 96 | -- 暂存本次请求的rsp key,smbus发送时随请求带给service,失败回传后用于完整匹配唤醒 |
| 96 | self.cur_rsp_key = key | 97 | self.cur_rsp_key = key |
| 97 | - local ret, rsp = self.mctp_engine:request(key, cb, timeout_ms, more_process_cb) | 98 | + local ret, rsp = self.mctp_engine:request(key, cb, timeout_ms, more_process_cb, nil, self) |
| 98 | if ret == E_TIMEOUT then | 99 | if ret == E_TIMEOUT then |
| 99 | self.req_timeout_count = self.req_timeout_count + 1 | 100 | self.req_timeout_count = self.req_timeout_count + 1 |
| 100 | elseif ret == E_ERROR then | 101 | elseif ret == E_ERROR then |
| @@ -121,6 +122,9 @@ function c_ep_mctp:send(cb) | |||
| 121 | end | 122 | end |
| 122 | 123 | ||
| 123 | function c_ep_mctp:prepare_mctp_request(id) | 124 | function c_ep_mctp:prepare_mctp_request(id) |
| 125 | + if self.retired then | ||
| 126 | + error(errors.mctp_endpoint_reset(), 0) | ||
| 127 | + end | ||
| 124 | local mctp_engine = self.mctp_engine | 128 | local mctp_engine = self.mctp_engine |
| 125 | local cnt = 0 | 129 | local cnt = 0 |
| 126 | local mctp_ctx = self.mctp_ctx | 130 | local mctp_ctx = self.mctp_ctx |
| @@ -66,7 +66,11 @@ skynet.timer = { | |||
| 66 | cb() | 66 | cb() |
| 67 | end | 67 | end |
| 68 | end, | 68 | end, |
| 69 | - stop = function(...) end, | 69 | + stop = function(timer) |
| 70 | + if timer and timer.stop then | ||
| 71 | + timer:stop() | ||
| 72 | + end | ||
| 73 | + end, | ||
| 70 | } | 74 | } |
| 71 | 75 | ||
| 72 | return skynet | 76 | return skynet |
| @@ -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-test', | ||
| 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-test', | ||
| 161 | target_eid, | 163 | target_eid, |
| 162 | ref_chip, | 164 | ref_chip, |
| 163 | medium_type, | 165 | medium_type, |
| @@ -150,3 +150,12 @@ function TestMctpEidTable:test_find_reg_info() | |||
| 150 | res = self.eid_table:find_reg_info(reg_info.pid, reg_info.msg_src_eid, reg_info.phy_addr, reg_info.msg_type) | 150 | res = self.eid_table:find_reg_info(reg_info.pid, reg_info.msg_src_eid, reg_info.phy_addr, reg_info.msg_type) |
| 151 | lu.assertEquals(res, nil) | 151 | lu.assertEquals(res, nil) |
| 152 | end | 152 | end |
| 153 | + | ||
| 154 | +function TestMctpEidTable:test_message_type_is_an_immutable_registration_key() | ||
| 155 | + local registry = c_mctp_eid_table.new(1) | ||
| 156 | + registry:add_reg_info(1, 1, 9, 32, 2, 'ncsi') | ||
| 157 | + registry:add_reg_info(1, 1, 9, 32, 126, 'vendor') | ||
| 158 | + lu.assertEquals(#registry.reg_table, 2) | ||
| 159 | + lu.assertEquals(registry:find_reg_info(1, 9, 32, 2).module_name, 'ncsi') | ||
| 160 | + lu.assertEquals(registry:find_reg_info(1, 9, 32, 126).module_name, 'vendor') | ||
| 161 | +end | ||
| @@ -43,3 +43,100 @@ function TestEndpointGeneral:test_request() | |||
| 43 | ret, _ = pcall(self.send, self, req_bin) | 43 | ret, _ = pcall(self.send, self, req_bin) |
| 44 | lu.assertEquals(ret, true) | 44 | lu.assertEquals(ret, true) |
| 45 | end | 45 | end |
| 46 | + | ||
| 47 | +TestPeerRequestLifetime = {} | ||
| 48 | +function TestPeerRequestLifetime:setup() | ||
| 49 | + self.engine = c_mctp_engine.new(nil, 1) | ||
| 50 | + self.coroutines = {} | ||
| 51 | + self.skynet = require('skynet') | ||
| 52 | +end | ||
| 53 | + | ||
| 54 | +function TestPeerRequestLifetime:teardown() | ||
| 55 | + for _, co in ipairs(self.coroutines) do | ||
| 56 | + if coroutine.status(co) == 'suspended' then | ||
| 57 | + coroutine.resume(co) | ||
| 58 | + end | ||
| 59 | + end | ||
| 60 | +end | ||
| 61 | + | ||
| 62 | +function TestPeerRequestLifetime:start_request(ep, consumer) | ||
| 63 | + local co = coroutine.create(function() | ||
| 64 | + return require('mctp_endpoints.ep_mctp').request(ep, 1, function() end, 1000, consumer) | ||
| 65 | + end) | ||
| 66 | + self.coroutines[#self.coroutines + 1] = co | ||
| 67 | + local old_wait = self.skynet.wait | ||
| 68 | + self.skynet.wait = coroutine.yield | ||
| 69 | + local ok, err = coroutine.resume(co) | ||
| 70 | + self.skynet.wait = old_wait -- resume包含协程内异常;所有断言之前恢复共享模块。 | ||
| 71 | + lu.assertTrue(ok, tostring(err)) | ||
| 72 | + lu.assertEquals(coroutine.status(co), 'suspended') | ||
| 73 | + return co | ||
| 74 | +end | ||
| 75 | + | ||
| 76 | +function TestPeerRequestLifetime:test_failed_request_always_restores_wait_before_assertions() | ||
| 77 | + local original = self.skynet.wait | ||
| 78 | + lu.assertFalse(pcall(self.start_request, self, {})) | ||
| 79 | + lu.assertIs(self.skynet.wait, original) | ||
| 80 | +end | ||
| 81 | + | ||
| 82 | +function TestPeerRequestLifetime:test_retired_endpoint_rejects_new_request_and_send_as_reset() | ||
| 83 | + local ep = self.engine.endpoints:create_endpoint(32, 9, 15, 15) | ||
| 84 | + self.engine:retire_peer(15, ep.phy_addr, 9) | ||
| 85 | + local base, calls = require('mctp_endpoints.ep_mctp'), 0 | ||
| 86 | + local cb = function() | ||
| 87 | + calls = calls + 1 | ||
| 88 | + end | ||
| 89 | + local ok, err = pcall(base.request, ep, 1, cb, 1000) | ||
| 90 | + lu.assertFalse(ok) | ||
| 91 | + lu.assertStrContains(tostring(err), 'MctpEndpointReset') | ||
| 92 | + ok, err = pcall(base.send, ep, cb) | ||
| 93 | + lu.assertFalse(ok) | ||
| 94 | + lu.assertStrContains(tostring(err), 'MctpEndpointReset') | ||
| 95 | + lu.assertEquals(calls, 0) | ||
| 96 | +end | ||
| 97 | + | ||
| 98 | +function TestPeerRequestLifetime:test_retirement_cancels_only_matching_query_and_skips_consumer() | ||
| 99 | + local engine, calls = self.engine, 0 | ||
| 100 | + local old = engine.endpoints:create_endpoint(32, 9, 15, 15) | ||
| 101 | + local other = engine.endpoints:create_endpoint(33, 10, 15, 15) | ||
| 102 | + local cancelled = self:start_request(old, function() | ||
| 103 | + calls = calls + 1 | ||
| 104 | + end) | ||
| 105 | + local live = self:start_request(other) | ||
| 106 | + engine:retire_peer(15, old.phy_addr, 9) | ||
| 107 | + lu.assertTrue(old.retired) | ||
| 108 | + lu.assertIsNil(engine.endpoints:get_endpoint(9, 15, 15)) | ||
| 109 | + lu.assertIs(engine.endpoints:get_endpoint(10, 15, 15), other) | ||
| 110 | + local ok, err = coroutine.resume(cancelled) | ||
| 111 | + lu.assertFalse(ok) | ||
| 112 | + lu.assertStrContains(tostring(err), 'MctpEndpointReset') | ||
| 113 | + lu.assertEquals(calls, 0) | ||
| 114 | + lu.assertEquals(coroutine.status(live), 'suspended') | ||
| 115 | + local waits = 0 | ||
| 116 | + for _, wait in pairs(engine.rsp_wait_list) do | ||
| 117 | + lu.assertIs(wait.endpoint, other) | ||
| 118 | + waits = waits + 1 | ||
| 119 | + end | ||
| 120 | + lu.assertEquals(waits, 1) | ||
| 121 | + lu.assertFalse(pcall(old.prepare_mctp_request, old)) | ||
| 122 | +end | ||
| 123 | + | ||
| 124 | +function TestPeerRequestLifetime:test_retirement_stops_wait_timer_once_before_resume() | ||
| 125 | + local engine, stops = self.engine, 0 | ||
| 126 | + engine.new_timer = function() | ||
| 127 | + return { | ||
| 128 | + stop = function() | ||
| 129 | + stops = stops + 1 | ||
| 130 | + end, | ||
| 131 | + } | ||
| 132 | + end | ||
| 133 | + local ep = engine.endpoints:create_endpoint(32, 9, 15, 15) | ||
| 134 | + local co = self:start_request(ep) | ||
| 135 | + engine:retire_peer(15, ep.phy_addr, 9) | ||
| 136 | + lu.assertEquals(stops, 1) | ||
| 137 | + engine:retire_peer(15, ep.phy_addr, 9) | ||
| 138 | + local ok, err = coroutine.resume(co) | ||
| 139 | + lu.assertFalse(ok) | ||
| 140 | + lu.assertStrContains(tostring(err), 'MctpEndpointReset') | ||
| 141 | + lu.assertEquals(stops, 1) | ||
| 142 | +end | ||
| @@ -276,3 +276,217 @@ function TestMctpEngine:test_start() | |||
| 276 | 276 | ||
| 277 | mctp_engine.hw_pcie_proto = old_hw_pcie_proto | 277 | mctp_engine.hw_pcie_proto = old_hw_pcie_proto |
| 278 | end | 278 | end |
| 279 | + | ||
| 280 | +TestPeerLifetime = {} | ||
| 281 | +function TestPeerLifetime:setup() | ||
| 282 | + self.engine = c_mctp_engine.new(nil, 1) -- mc.class.new already invokes init. | ||
| 283 | + self.engine.config_eid_table = function(engine, reg) | ||
| 284 | + engine.endpoints:create_endpoint(reg.phy_addr, reg.msg_src_eid, reg.msg_type, reg.medium_type, reg.ep_status) | ||
| 285 | + end | ||
| 286 | +end | ||
| 287 | + | ||
| 288 | +function TestPeerLifetime:test_non_pcie_route_removal_keeps_same_address_pcie_peer() | ||
| 289 | + local engine = self.engine | ||
| 290 | + engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'card') | ||
| 291 | + local ep = engine.endpoints:get_endpoint(9, 126, 15) | ||
| 292 | + engine.routing_table.on_del_entry:emit({ phys_address = 32, phys_transport_binding_id = 1 }) | ||
| 293 | + lu.assertIs(engine.endpoints:get_endpoint(9, 126, 15), ep) | ||
| 294 | + lu.assertFalse(ep.retired == true) | ||
| 295 | + lu.assertFalse(engine.eid_table.reg_table[1].route_withdrawn == true) | ||
| 296 | +end | ||
| 297 | + | ||
| 298 | +function TestPeerLifetime:test_pcie_registry_changes_do_not_retire_smbus_endpoint() | ||
| 299 | + local engine = self.engine | ||
| 300 | + local medium = mctp_def.PHY_MEDIUM_SMBUS_OVER_I2COVERLOCALBUS | ||
| 301 | + local smbus = engine.endpoints:create_endpoint(32, 9, 126, medium) | ||
| 302 | + engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'pcie') | ||
| 303 | + lu.assertEquals(engine.eid_table.reg_table[1].medium_type, mctp_def.PHY_MEDIUM_PCI) | ||
| 304 | + engine.eid_table:add_reg_info(1, 1, 10, 33, 126, 'pcie') | ||
| 305 | + lu.assertIs(engine.endpoints:get_endpoint(9, 126, medium), smbus) | ||
| 306 | + lu.assertFalse(smbus.retired == true) | ||
| 307 | + engine.eid_table:reset() | ||
| 308 | + lu.assertIs(engine.endpoints:get_endpoint(9, 126, medium), smbus) | ||
| 309 | + lu.assertFalse(smbus.retired == true) | ||
| 310 | +end | ||
| 311 | + | ||
| 312 | +function TestPeerLifetime:test_registration_change_retires_old_identity_before_replacement() | ||
| 313 | + local table = self.engine.eid_table | ||
| 314 | + table:add_reg_info(1, 1, 9, 32, 126, 'card') | ||
| 315 | + table:add_reg_info(1, 1, 12, 33, 126, 'other') | ||
| 316 | + local old = self.engine.endpoints:get_endpoint(9, 126, 15) | ||
| 317 | + table:add_reg_info(1, 1, 10, 32, 126, 'card') | ||
| 318 | + lu.assertTrue(old.retired) | ||
| 319 | + lu.assertIsNil(self.engine.endpoints:get_endpoint(9, 126, 15)) | ||
| 320 | + lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 126, 15)) | ||
| 321 | + lu.assertNotNil(self.engine.endpoints:get_endpoint(12, 126, 15)) | ||
| 322 | + local changed = self.engine.endpoints:get_endpoint(10, 126, 15) | ||
| 323 | + table:add_reg_info(1, 1, 10, 34, 126, 'card') | ||
| 324 | + lu.assertTrue(changed.retired) | ||
| 325 | + lu.assertEquals(self.engine.endpoints:get_endpoint(10, 126, 15).phy_addr, 0x2200) | ||
| 326 | +end | ||
| 327 | + | ||
| 328 | +function TestPeerLifetime:test_route_rebind_changes_eid_and_restores_same_eid_without_stale_endpoints() | ||
| 329 | + local routing, registry = self.engine.routing_table, self.engine.eid_table | ||
| 330 | + local function update(eid, port) | ||
| 331 | + routing:update({ | ||
| 332 | + { | ||
| 333 | + phys_address = 32, | ||
| 334 | + starting_eid = eid, | ||
| 335 | + eid_range_size = 1, | ||
| 336 | + phys_transport_binding_id = 2, | ||
| 337 | + phys_media_type_id = 11, | ||
| 338 | + port_num = port or 0, | ||
| 339 | + }, | ||
| 340 | + }) | ||
| 341 | + end | ||
| 342 | + update(9) | ||
| 343 | + registry:add_reg_info(1, 1, 9, 32, 2, 'card') | ||
| 344 | + registry:add_reg_info(1, 1, 9, 32, 126, 'card') | ||
| 345 | + registry:add_reg_info(1, 1, 12, 33, 126, 'other') | ||
| 346 | + local first = self.engine.endpoints:get_endpoint(9, 126, 15) | ||
| 347 | + update(10) | ||
| 348 | + lu.assertTrue(first.retired) | ||
| 349 | + lu.assertIsNil(self.engine.endpoints:get_endpoint(9, 126, 15)) | ||
| 350 | + local next_peer = self.engine.endpoints:get_endpoint(10, 126, 15) | ||
| 351 | + lu.assertNotNil(next_peer) | ||
| 352 | + lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 2, 15)) | ||
| 353 | + lu.assertNotNil(self.engine.endpoints:get_endpoint(12, 126, 15)) | ||
| 354 | + update(10) | ||
| 355 | + lu.assertIs(self.engine.endpoints:get_endpoint(10, 126, 15), next_peer) | ||
| 356 | + update(10, 1) | ||
| 357 | + lu.assertTrue(next_peer.retired) | ||
| 358 | + lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 2, 15)) | ||
| 359 | + routing:update({}) | ||
| 360 | + lu.assertIsNil(self.engine.endpoints:get_endpoint(10, 126, 15)) | ||
| 361 | + lu.assertEquals(registry.reg_table[1].ep_status, mctp_def.ENDPOINT_UN_DISCOVERY) | ||
| 362 | + update(10, 1) | ||
| 363 | + lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 2, 15)) | ||
| 364 | + lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 126, 15)) | ||
| 365 | +end | ||
| 366 | + | ||
| 367 | +function TestPeerLifetime:test_driver_registration_failure_is_visible_and_retried_on_route_refresh() | ||
| 368 | + local engine, tries = self.engine, 0 | ||
| 369 | + engine.config_eid_table = c_mctp_engine.config_eid_table | ||
| 370 | + engine.hw_pcie_proto = { | ||
| 371 | + config_eid_table = function() | ||
| 372 | + tries = tries + 1 | ||
| 373 | + return tries > 1 | ||
| 374 | + end, | ||
| 375 | + } | ||
| 376 | + local route = { phys_address = 32, starting_eid = 9, eid_range_size = 1, phys_transport_binding_id = 2 } | ||
| 377 | + engine.routing_table:update({ route }) | ||
| 378 | + engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'card') | ||
| 379 | + local reg = engine.eid_table.reg_table[1] | ||
| 380 | + lu.assertEquals(tries, 1, 'initial driver attempts') | ||
| 381 | + lu.assertEquals(reg.ep_status, mctp_def.ENDPOINT_UN_DISCOVERY) | ||
| 382 | + lu.assertIsNil(engine.endpoints:get_endpoint(9, 126, 15)) | ||
| 383 | + engine.routing_table:update({ route }) | ||
| 384 | + lu.assertEquals(tries, 2) | ||
| 385 | + lu.assertEquals(reg.ep_status, mctp_def.ENDPOINT_IS_DISCOVERY) | ||
| 386 | + lu.assertNotNil(engine.endpoints:get_endpoint(9, 126, 15)) | ||
| 387 | +end | ||
| 388 | + | ||
| 389 | +function TestPeerLifetime:test_multiple_consumers_rebind_without_reviving_old_or_retiring_new_identity() | ||
| 390 | + local registry = self.engine.eid_table | ||
| 391 | + registry:add_reg_info(1, 1, 9, 32, 2, 'ncsi') | ||
| 392 | + registry:add_reg_info(1, 1, 9, 32, 126, 'vendor') | ||
| 393 | + registry:add_reg_info(1, 1, 10, 32, 2, 'ncsi') | ||
| 394 | + lu.assertEquals(registry:find_reg_info(1, 9, 32, 126).ep_status, mctp_def.ENDPOINT_UN_DISCOVERY) | ||
| 395 | + local new = self.engine.endpoints:get_endpoint(10, 2, 15) | ||
| 396 | + registry:add_reg_info(1, 1, 10, 32, 126, 'vendor') | ||
| 397 | + lu.assertIs(self.engine.endpoints:get_endpoint(10, 2, 15), new) | ||
| 398 | + lu.assertFalse(new.retired == true) | ||
| 399 | + lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 126, 15)) | ||
| 400 | +end | ||
| 401 | + | ||
| 402 | +function TestPeerLifetime:test_two_cards_can_swap_eids_in_either_route_order() | ||
| 403 | + for _, reversed in ipairs({ false, true }) do | ||
| 404 | + self:setup() | ||
| 405 | + local engine, registry = self.engine, self.engine.eid_table | ||
| 406 | + local function route(phy, eid) | ||
| 407 | + return { phys_address = phy, starting_eid = eid, eid_range_size = 1, phys_transport_binding_id = 2 } | ||
| 408 | + end | ||
| 409 | + engine.routing_table:update({ route(32, 9), route(33, 10) }) | ||
| 410 | + registry:add_reg_info(1, 1, 9, 32, 126, 'A') | ||
| 411 | + registry:add_reg_info(1, 1, 10, 33, 126, 'B') | ||
| 412 | + local first, second = registry.reg_table[1], registry.reg_table[2] | ||
| 413 | + local a, b = engine.endpoints:get_endpoint(9, 126, 15), engine.endpoints:get_endpoint(10, 126, 15) | ||
| 414 | + local next_routes = reversed and { route(33, 9), route(32, 10) } or { route(32, 10), route(33, 9) } | ||
| 415 | + engine.routing_table:update(next_routes) | ||
| 416 | + lu.assertEquals(#registry.reg_table, 2) | ||
| 417 | + lu.assertEquals({ first.phy_addr, first.msg_src_eid, first.module_name }, { 32, 10, 'A' }) | ||
| 418 | + lu.assertEquals({ second.phy_addr, second.msg_src_eid, second.module_name }, { 33, 9, 'B' }) | ||
| 419 | + lu.assertTrue(a.retired) | ||
| 420 | + lu.assertTrue(b.retired) | ||
| 421 | + lu.assertEquals(engine.endpoints:get_endpoint(10, 126, 15).phy_addr, 0x2000) | ||
| 422 | + lu.assertEquals(engine.endpoints:get_endpoint(9, 126, 15).phy_addr, 0x2100) | ||
| 423 | + end | ||
| 424 | +end | ||
| 425 | + | ||
| 426 | +function TestPeerLifetime:test_failed_registration_recovers_after_eid_changes() | ||
| 427 | + local engine, attempts = self.engine, 0 | ||
| 428 | + engine.config_eid_table = c_mctp_engine.config_eid_table | ||
| 429 | + engine.hw_pcie_proto = { | ||
| 430 | + config_eid_table = function() | ||
| 431 | + attempts = attempts + 1 | ||
| 432 | + return attempts > 1 | ||
| 433 | + end, | ||
| 434 | + } | ||
| 435 | + engine.routing_table:update({ | ||
| 436 | + { phys_address = 32, starting_eid = 9, eid_range_size = 1, phys_transport_binding_id = 2 }, | ||
| 437 | + }) | ||
| 438 | + engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'card') | ||
| 439 | + lu.assertEquals(engine.eid_table.reg_table[1].ep_status, mctp_def.ENDPOINT_UN_DISCOVERY) | ||
| 440 | + engine.routing_table:update({ | ||
| 441 | + { phys_address = 32, starting_eid = 10, eid_range_size = 1, phys_transport_binding_id = 2 }, | ||
| 442 | + }) | ||
| 443 | + local reg = engine.eid_table.reg_table[1] | ||
| 444 | + lu.assertEquals(attempts, 2) | ||
| 445 | + lu.assertEquals({ reg.phy_addr, reg.msg_src_eid, reg.ep_status }, { 32, 10, mctp_def.ENDPOINT_IS_DISCOVERY }) | ||
| 446 | + lu.assertIsNil(engine.endpoints:get_endpoint(9, 126, 15)) | ||
| 447 | + lu.assertNotNil(engine.endpoints:get_endpoint(10, 126, 15)) | ||
| 448 | +end | ||
| 449 | + | ||
| 450 | +function TestPeerLifetime:test_reused_eid_does_not_deliver_old_physical_source() | ||
| 451 | + local engine, received = self.engine, {} | ||
| 452 | + local ep = engine.endpoints:create_endpoint(33, 9, 126, 15) | ||
| 453 | + ep.on_receive:on(function(data) | ||
| 454 | + received[#received + 1] = data | ||
| 455 | + end) | ||
| 456 | + local physical, ctx = mctp.pcie_vdm_ctx.new(), mctp.mctp_ctx.new() | ||
| 457 | + ctx:set_prev(physical) | ||
| 458 | + ctx.src, ctx.dest, ctx.tag_owner = 9, 11, true | ||
| 459 | + physical.remote_phy_addr = 0x2000 | ||
| 460 | + engine:rx(ctx, 'old notification', 126, 15) | ||
| 461 | + ctx.tag_owner = false | ||
| 462 | + engine:rx(ctx, 'old unmatched response', 126, 15) | ||
| 463 | + lu.assertEquals(received, {}) | ||
| 464 | + physical.remote_phy_addr = 0x2100 | ||
| 465 | + engine:rx(ctx, 'current legacy notification', 126, 15) | ||
| 466 | + ctx.tag_owner = true | ||
| 467 | + engine:rx(ctx, 'current notification', 126, 15) | ||
| 468 | + lu.assertEquals(received, { 'current legacy notification', 'current notification' }) | ||
| 469 | +end | ||
| 470 | + | ||
| 471 | +function TestPeerLifetime:test_pcie_requires_physical_context_and_wire_parser_supplies_it() | ||
| 472 | + local engine, received = self.engine, {} | ||
| 473 | + local ep = engine.endpoints:create_endpoint(33, 9, 126, 15) | ||
| 474 | + ep.on_receive:on(function(data) | ||
| 475 | + received[#received + 1] = data | ||
| 476 | + end) | ||
| 477 | + local ctx = mctp.mctp_ctx.new() | ||
| 478 | + ctx.src, ctx.dest, ctx.tag_owner = 9, 11, true | ||
| 479 | + lu.assertIsNil(ctx:get('remote_phy_addr', 'pcie_vdm_ctx')) | ||
| 480 | + engine:rx(ctx, 'missing source', 126, 15) | ||
| 481 | + lu.assertEquals(received, {}) | ||
| 482 | + | ||
| 483 | + local pcie, transport = mctp.pcie_vdm_protocol.new(), mctp.mctp_protocol.new() | ||
| 484 | + transport.pcie_eid = 11 | ||
| 485 | + pcie:add_children(transport) | ||
| 486 | + transport:set_rx_handler(function(wire_ctx, data) | ||
| 487 | + lu.assertEquals(wire_ctx:get('remote_phy_addr', 'pcie_vdm_ctx'), 0x2100) | ||
| 488 | + return engine:rx(wire_ctx, data, 126, 15) | ||
| 489 | + end) | ||
| 490 | + pcie:pop('\x72\x00\x10\x01\x21\x00\x10\x7f\x03\x00\x1a\xb4\x01\x0b\x09\xd9\x7e\x8a\x0b', nil, 15) | ||
| 491 | + lu.assertEquals(received, { '\x7e\x8a\x0b' }) | ||
| 492 | +end | ||
| @@ -22,6 +22,79 @@ end | |||
| 22 | 22 | ||
| 23 | function TestMctpMdbMgmt:teardownClass() end | 23 | function TestMctpMdbMgmt:teardownClass() end |
| 24 | 24 | ||
| 25 | +function TestMctpMdbMgmt:test_rebind_updates_dispatch_and_disconnects_old_notifications() | ||
| 26 | + local engine = c_mctp_engine.new(nil, 1) | ||
| 27 | + engine:init() | ||
| 28 | + engine.endpoints:init() | ||
| 29 | + local first = engine.endpoints:create_endpoint(32, 10, 126, 15) | ||
| 30 | + local second = engine.endpoints:create_endpoint(32, 11, 126, 15) | ||
| 31 | + local obj = { path = '/test/endpoint', TargetPhyAddr = 32, TargetEid = 9, MessageType = 126, MediumType = 15 } | ||
| 32 | + local messages = {} | ||
| 33 | + local mgr = setmetatable({ | ||
| 34 | + system_id = 1, | ||
| 35 | + mctp_engine = engine, | ||
| 36 | + mdb_endpoints = { { obj = obj } }, | ||
| 37 | + ep_info = { [obj.path] = { TargetEid = 9 } }, | ||
| 38 | + app = { | ||
| 39 | + EndpointPCIeEndpointOnMessage = function(_, target, data) | ||
| 40 | + lu.assertIs(target, obj) | ||
| 41 | + messages[#messages + 1] = data | ||
| 42 | + end, | ||
| 43 | + EndpointPCIeEndpointMessageReceived = function() end, | ||
| 44 | + }, | ||
| 45 | + }, { __index = c_mctp_mdb_mgmt }) | ||
| 46 | + mgr:upd_mdb_endpoint({ phy_addr = 32, msg_src_eid = 10, msg_type = 126, medium_type = 15, module_name = 'consumer' }) | ||
| 47 | + first.on_receive:emit('first') | ||
| 48 | + mgr:upd_mdb_endpoint({ phy_addr = 32, msg_src_eid = 11, msg_type = 126, medium_type = 15, module_name = 'consumer' }) | ||
| 49 | + first.on_receive:emit('obsolete') | ||
| 50 | + second.on_receive:emit('second') | ||
| 51 | + lu.assertEquals(messages, { 'first', 'second' }) | ||
| 52 | + lu.assertEquals(mgr.ep_info[obj.path], { TargetEid = 11, MessageType = 126, MediumType = 15 }) | ||
| 53 | + mgr:trace_message(obj, 'start') | ||
| 54 | + lu.assertEquals(second.is_trace, 'start') | ||
| 55 | + lu.assertNotEquals(first.is_trace, 'start') | ||
| 56 | +end | ||
| 57 | + | ||
| 58 | +function TestMctpMdbMgmt:test_delayed_initial_subscription_does_not_duplicate_rebind() | ||
| 59 | + local signal, skynet = require('mc.signal'), require('skynet') | ||
| 60 | + local ep, messages = { on_receive = signal.new() }, 0 | ||
| 61 | + local engine = { | ||
| 62 | + module_name = 'MCTP', | ||
| 63 | + endpoints = { | ||
| 64 | + get_endpoint = function() | ||
| 65 | + return ep | ||
| 66 | + end, | ||
| 67 | + }, | ||
| 68 | + } | ||
| 69 | + local app = { | ||
| 70 | + CreateEndpoint = function(_, _, _, _, cb) | ||
| 71 | + local obj = { path = '/test/delayed' } | ||
| 72 | + cb(obj) | ||
| 73 | + return obj | ||
| 74 | + end, | ||
| 75 | + EndpointPCIeEndpointOnMessage = function() | ||
| 76 | + messages = messages + 1 | ||
| 77 | + end, | ||
| 78 | + EndpointPCIeEndpointMessageReceived = function() end, | ||
| 79 | + } | ||
| 80 | + local mgr = setmetatable( | ||
| 81 | + { system_id = 1, app = app, mctp_engine = engine, mdb_endpoints = {}, ep_info = {} }, | ||
| 82 | + { __index = c_mctp_mdb_mgmt } | ||
| 83 | + ) | ||
| 84 | + local reg = { phy_addr = 32, msg_src_eid = 10, msg_type = 126, medium_type = 15, module_name = 'consumer' } | ||
| 85 | + local original, delayed = skynet.fork | ||
| 86 | + skynet.fork = function(fn) | ||
| 87 | + delayed = fn | ||
| 88 | + end | ||
| 89 | + local ok, err = pcall(mgr.add_mdb_endpoint, mgr, reg) | ||
| 90 | + skynet.fork = original | ||
| 91 | + lu.assertTrue(ok, tostring(err)) | ||
| 92 | + mgr:upd_mdb_endpoint(reg) | ||
| 93 | + delayed() | ||
| 94 | + ep.on_receive:emit('one') | ||
| 95 | + lu.assertEquals(messages, 1) | ||
🔵 Low Priority 新增用例(test_mctp_mdb_mgmt.lua:58-96)只覆盖了"初始订阅尚未完成 → 重绑 → 延迟 fork 不重复订阅"这条分支;本次新增的 未被覆盖的高风险逻辑: 漏测的具体回归:若 建议:补充"add → del(fork 未执行)→ 执行被捕获的 fork → 断言不再投递"的用例,覆盖 o.active=false 守卫。 ![]() ![]() Incorrect? | |||
| 96 | +end | ||
| 97 | + | ||
| 25 | function TestMctpMdbMgmt:test_add_objects() | 98 | function TestMctpMdbMgmt:test_add_objects() |
| 26 | local ep_obj = { | 99 | local ep_obj = { |
| 27 | name = 'Endpoint_1_0101', | 100 | name = 'Endpoint_1_0101', |
| @@ -159,3 +232,152 @@ function TestMctpMdbMgmt:test_del_objects_callback() | |||
| 159 | pcall(c_mctp_mdb_mgmt.del_objects_callback, c_mctp_mdb_mgmt, 'Endpoint', { name = 'Enponit_test' }, '01') | 232 | pcall(c_mctp_mdb_mgmt.del_objects_callback, c_mctp_mdb_mgmt, 'Endpoint', { name = 'Enponit_test' }, '01') |
| 160 | lu.assertNotIsNil(ret) | 233 | lu.assertNotIsNil(ret) |
| 161 | end | 234 | end |
| 235 | + | ||
| 236 | +local function lifecycle_fixture() | ||
| 237 | + local skynet = require('skynet') | ||
| 238 | + local original = package.loaded['mc.class_mgnt'] | ||
| 239 | + local removed, messages, delayed = {}, {}, {} | ||
| 240 | + package.loaded['mc.class_mgnt'] = function() | ||
| 241 | + return { | ||
| 242 | + remove = function(_, obj) | ||
| 243 | + removed[#removed + 1] = obj.path | ||
| 244 | + end, | ||
| 245 | + } | ||
| 246 | + end | ||
| 247 | + local ok, Module = pcall(dofile, package.searchpath('mctp_mdb_mgmt', package.path)) | ||
| 248 | + package.loaded['mc.class_mgnt'] = original | ||
| 249 | + lu.assertTrue(ok, tostring(Module)) | ||
| 250 | + local engine = c_mctp_engine.new(nil, 1) | ||
| 251 | + local app = { | ||
| 252 | + CreateEndpoint = function(_, _, phy, typ, cb) | ||
| 253 | + local obj = { path = '/test/' .. phy .. '/' .. typ, class_name = 'Endpoint' } | ||
| 254 | + cb(obj) | ||
| 255 | + return obj | ||
| 256 | + end, | ||
| 257 | + EndpointPCIeEndpointOnMessage = function(_, obj, data) | ||
| 258 | + messages[#messages + 1] = { obj.path, data } | ||
| 259 | + end, | ||
| 260 | + EndpointPCIeEndpointMessageReceived = function() end, | ||
| 261 | + } | ||
| 262 | + local mgr = setmetatable( | ||
| 263 | + { system_id = 1, app = app, mctp_engine = engine, mdb_endpoints = {}, ep_info = {} }, | ||
| 264 | + { __index = Module } | ||
| 265 | + ) | ||
| 266 | + local function call(method, ...) | ||
| 267 | + local old = skynet.fork | ||
| 268 | + skynet.fork = function(fn) | ||
| 269 | + delayed[#delayed + 1] = fn | ||
| 270 | + end | ||
| 271 | + local success, err = pcall(method, mgr, ...) | ||
| 272 | + skynet.fork = old | ||
| 273 | + lu.assertTrue(success, tostring(err)) | ||
| 274 | + end | ||
| 275 | + return mgr, engine, removed, messages, delayed, call | ||
| 276 | +end | ||
| 277 | + | ||
| 278 | +function TestMctpMdbMgmt:test_delete_before_initial_fork_cannot_resurrect_subscription() | ||
| 279 | + local mgr, engine, removed, messages, delayed, call = lifecycle_fixture() | ||
| 280 | + local ep = engine.endpoints:create_endpoint(32, 9, 126, 15) | ||
| 281 | + local reg = { phy_addr = 32, msg_src_eid = 9, msg_type = 126, medium_type = 15, module_name = 'card' } | ||
| 282 | + call(mgr.add_mdb_endpoint, reg) | ||
| 283 | + mgr:del_mdb_endpoint(reg) | ||
| 284 | + delayed[1]() | ||
| 285 | + ep.on_receive:emit('late') | ||
| 286 | + mgr:del_mdb_endpoint(reg) | ||
| 287 | + lu.assertEquals(messages, {}) | ||
| 288 | + lu.assertEquals(removed, { '/test/32/126' }) | ||
| 289 | + lu.assertEquals(mgr.ep_info, {}) | ||
| 290 | + lu.assertEquals(mgr.mdb_endpoints, {}) | ||
| 291 | +end | ||
| 292 | + | ||
| 293 | +function TestMctpMdbMgmt:test_physical_move_recreates_path_and_disconnects_old_notifications() | ||
| 294 | + local mgr, engine, removed, messages, delayed, call = lifecycle_fixture() | ||
| 295 | + local old = { phy_addr = 32, msg_src_eid = 9, msg_type = 126, medium_type = 15, module_name = 'card' } | ||
| 296 | + local new = { phy_addr = 33, msg_src_eid = 9, msg_type = 126, medium_type = 15, module_name = 'card' } | ||
| 297 | + local first = engine.endpoints:create_endpoint(32, 9, 126, 15) | ||
| 298 | + call(mgr.add_mdb_endpoint, old) | ||
| 299 | + delayed[1]() | ||
| 300 | + engine:retire_peer(15, first.phy_addr, 9) | ||
| 301 | + local second = engine.endpoints:create_endpoint(33, 9, 126, 15) | ||
| 302 | + call(mgr.upd_mdb_endpoint, new, old) | ||
| 303 | + lu.assertEquals(removed, { '/test/32/126' }) | ||
| 304 | + delayed[2]() | ||
| 305 | + first.on_receive:emit('old') | ||
| 306 | + second.on_receive:emit('new') | ||
| 307 | + lu.assertEquals(messages, { { '/test/33/126', 'new' } }) | ||
| 308 | + lu.assertIsNil(mgr.ep_info['/test/32/126']) | ||
| 309 | + lu.assertEquals(mgr.ep_info['/test/33/126'], { MediumType = 15, TargetEid = 9, MessageType = 126 }) | ||
| 310 | +end | ||
| 311 | + | ||
| 312 | +function TestMctpMdbMgmt:test_swapped_eids_keep_physical_paths_and_new_notifications() | ||
| 313 | + for _, reversed in ipairs({ false, true }) do | ||
| 314 | + local mgr, engine, removed, messages, delayed, call = lifecycle_fixture() | ||
| 315 | + engine.config_eid_table = function(e, reg) | ||
| 316 | + e.endpoints:create_endpoint(reg.phy_addr, reg.msg_src_eid, reg.msg_type, reg.medium_type, reg.ep_status) | ||
| 317 | + end | ||
| 318 | + engine.eid_table.on_add_reg:on(function(reg) | ||
| 319 | + call(mgr.add_mdb_endpoint, reg) | ||
| 320 | + end) | ||
| 321 | + engine.eid_table.on_update_reg:on(function(reg, _, previous) | ||
| 322 | + call(mgr.upd_mdb_endpoint, reg, previous) | ||
| 323 | + end) | ||
| 324 | + local function route(phy, eid) | ||
| 325 | + return { phys_address = phy, starting_eid = eid, eid_range_size = 1, phys_transport_binding_id = 2 } | ||
| 326 | + end | ||
| 327 | + engine.routing_table:update({ route(32, 9), route(33, 10) }) | ||
| 328 | + engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'A') | ||
| 329 | + engine.eid_table:add_reg_info(1, 1, 10, 33, 126, 'B') | ||
| 330 | + local old_a, old_b = engine.endpoints:get_endpoint(9, 126, 15), engine.endpoints:get_endpoint(10, 126, 15) | ||
| 331 | + engine.routing_table:update(reversed and { route(33, 9), route(32, 10) } or { route(32, 10), route(33, 9) }) | ||
| 332 | + for _, fn in ipairs(delayed) do | ||
| 333 | + fn() | ||
| 334 | + end | ||
| 335 | + lu.assertEquals(removed, {}) | ||
| 336 | + lu.assertEquals(mgr.ep_info['/test/32/126'].TargetEid, 10) | ||
| 337 | + lu.assertEquals(mgr.ep_info['/test/33/126'].TargetEid, 9) | ||
| 338 | + old_a.on_receive:emit('old A') | ||
| 339 | + old_b.on_receive:emit('old B') | ||
| 340 | + engine.endpoints:get_endpoint(10, 126, 15).on_receive:emit('new A') | ||
| 341 | + engine.endpoints:get_endpoint(9, 126, 15).on_receive:emit('new B') | ||
| 342 | + lu.assertEquals(messages, { { '/test/32/126', 'new A' }, { '/test/33/126', 'new B' } }) | ||
| 343 | + end | ||
| 344 | +end | ||
| 345 | + | ||
| 346 | +function TestMctpMdbMgmt:test_withdrawn_endpoint_skips_subscription_and_recovers_once() | ||
| 347 | + for _, subscribed in ipairs({ false, true }) do | ||
| 348 | + local mgr, engine, _, messages, delayed, call = lifecycle_fixture() | ||
| 349 | + local reg = { | ||
| 350 | + phy_addr = 32, | ||
| 351 | + msg_src_eid = 9, | ||
| 352 | + msg_type = 126, | ||
| 353 | + medium_type = 15, | ||
| 354 | + module_name = 'card', | ||
| 355 | + ep_status = 1, | ||
| 356 | + } | ||
| 357 | + local old = engine.endpoints:create_endpoint(32, 9, 126, 15) | ||
| 358 | + call(mgr.add_mdb_endpoint, reg) | ||
| 359 | + if subscribed then | ||
| 360 | + delayed[1]() | ||
| 361 | + end | ||
| 362 | + engine:retire_peer(15, old.phy_addr, 9) | ||
| 363 | + local lookup, lookups = engine.endpoints.get_endpoint, 0 | ||
| 364 | + engine.endpoints.get_endpoint = function(...) | ||
| 365 | + lookups = lookups + 1 | ||
| 366 | + return lookup(...) | ||
| 367 | + end | ||
| 368 | + reg.ep_status = 0 | ||
| 369 | + mgr:upd_mdb_endpoint(reg) | ||
| 370 | + mgr:upd_mdb_endpoint(reg) | ||
| 371 | + delayed[1]() | ||
| 372 | + lu.assertEquals(lookups, 0) | ||
| 373 | + lu.assertIsNil(mgr.mdb_endpoints[1].signal) | ||
| 374 | + old.on_receive:emit('obsolete') | ||
| 375 | + lu.assertEquals(messages, {}) | ||
| 376 | + local current = engine.endpoints:create_endpoint(32, 9, 126, 15) | ||
| 377 | + reg.ep_status = 1 | ||
| 378 | + mgr:upd_mdb_endpoint(reg) | ||
| 379 | + delayed[1]() | ||
| 380 | + current.on_receive:emit('restored') | ||
| 381 | + lu.assertEquals(messages, { { '/test/32/126', 'restored' } }) | ||
| 382 | + end | ||
| 383 | +end | ||
| @@ -83,3 +83,41 @@ function TestMctpRoutingTable:test_get_route_table() | |||
| 83 | } | 83 | } |
| 84 | lu.assertEquals(res, expert) | 84 | lu.assertEquals(res, expert) |
| 85 | end | 85 | end |
| 86 | + | ||
| 87 | +function TestMctpRoutingTable:test_changed_route_retires_old_before_publishing_new() | ||
| 88 | + for _, field in ipairs({ | ||
| 89 | + 'starting_eid', | ||
| 90 | + 'eid_range_size', | ||
| 91 | + 'entry_type', | ||
| 92 | + 'phys_transport_binding_id', | ||
| 93 | + 'phys_media_type_id', | ||
| 94 | + 'port_num', | ||
| 95 | + 'dymatic_static_entry', | ||
| 96 | + }) do | ||
| 97 | + local table_ = c_mctp_routing_table.new(1) | ||
| 98 | + local old, changed, events = {}, {}, {} | ||
| 99 | + for k, v in pairs(entries[1]) do | ||
| 100 | + old[k], changed[k] = v, v | ||
| 101 | + end | ||
| 102 | + table_:update({ old }) | ||
| 103 | + changed[field] = changed[field] + 1 | ||
| 104 | + table_.on_del_entry:on(function(entry) | ||
| 105 | + lu.assertEquals(entry, old) | ||
| 106 | + lu.assertIsNil(table_:get_entry(entry.phys_address)) | ||
| 107 | + events[#events + 1] = 'del' | ||
| 108 | + end) | ||
| 109 | + table_.on_add_entry:on(function(entry) | ||
| 110 | + lu.assertEquals(entry, changed) | ||
| 111 | + events[#events + 1] = 'add' | ||
| 112 | + end) | ||
| 113 | + table_:update({ changed }) | ||
| 114 | + lu.assertEquals(events, { 'del', 'add' }, field) | ||
| 115 | + lu.assertEquals(table_:get_entry(old.phys_address), changed) | ||
| 116 | + local repeated = {} | ||
| 117 | + for key, value in pairs(changed) do | ||
| 118 | + repeated[key] = value | ||
| 119 | + end | ||
| 120 | + table_:update({ repeated }) | ||
| 121 | + lu.assertEquals(events, { 'del', 'add' }, '同值但不同表对象不重新发布') | ||
| 122 | + end | ||
| 123 | +end | ||


⚠️ 警告:路由撤销路径缺少 PCIe 传输类型守卫,存在非 PCIe 路由被错误退休的风险
on_del_entry→retire_peer的撤销路径没有像on_add_entry(mctp_engine.lua:174)和on_entry_updated(mctp_engine.lua:200)那样检查phys_transport_binding_id == MCTP_OVER_PCIE_ID。它只按reg.phy_addr == entry.phys_address匹配即退休 peer 并置route_withdrawn。而
on_add_entry/on_entry_updated的重注册路径都要求 PCIe 绑定,遇到非 PCIe(如 SMBus)路由会提前 return,不进行重注册。影响:若某个非 PCIe 路由条目恰好与已登记 peer 的
phys_address相同,删除该路由会退休对应 peer 并置route_withdrawn=true,但其后同物理地址的路由再添加时因非 PCIe 被跳过,peer 永远停留在撤销/未就绪状态,无法恢复。修复建议:在
on_del_entry的循环和retire_peer内与 peer 匹配前,同样加入phys_transport_binding_id == MCTP_OVER_PCIE_ID(或明确的 medium_type 匹配)守卫,保证撤销/重注册的传输过滤条件一致,避免非 PCIe 路由错误触发 PCIe peer 退休。