diff --git a/meson.build b/meson.build index 7975b7a7..5141b295 100644 --- a/meson.build +++ b/meson.build @@ -60,11 +60,13 @@ toml_dep = declare_dependency( executable('mctp', sources: ['src/mctp.c'] + netlink_sources + util_sources + ops_sources, install: true, + c_args: ['-DHAVE_LIBSYSTEMD=0'], ) mctp_test = executable('test-mctp', sources: ['src/mctp.c'] + netlink_sources + util_sources + test_ops_sources, include_directories: include_directories('src'), + c_args: ['-DHAVE_LIBSYSTEMD=0'], ) executable('mctp-req', @@ -92,6 +94,7 @@ if libsystemd.found() dependencies: [libsystemd, toml_dep], install: true, install_dir: get_option('sbindir'), + c_args: ['-DHAVE_LIBSYSTEMD=1'], ) mctpd_test = executable('test-mctpd', @@ -100,6 +103,7 @@ if libsystemd.found() ] + test_ops_sources + netlink_sources + util_sources, include_directories: include_directories('src'), dependencies: [libsystemd, toml_dep], + c_args: ['-DHAVE_LIBSYSTEMD=1'], ) endif diff --git a/src/mctp-ops.c b/src/mctp-ops.c index 0088dd31..15baded0 100644 --- a/src/mctp-ops.c +++ b/src/mctp-ops.c @@ -9,6 +9,9 @@ #include #include +#if HAVE_LIBSYSTEMD +#include +#endif #include #include "mctp.h" @@ -74,6 +77,12 @@ const struct mctp_ops mctp_ops = { .recvfrom = mctp_op_recvfrom, .close = mctp_op_close, }, +#if HAVE_LIBSYSTEMD + .sd_event = { + .add_time_relative = sd_event_add_time_relative, + .source_set_time_relative = sd_event_source_set_time_relative, + }, +#endif .bug_warn = mctp_bug_warn, }; diff --git a/src/mctp-ops.h b/src/mctp-ops.h index 105072ba..8348742b 100644 --- a/src/mctp-ops.h +++ b/src/mctp-ops.h @@ -7,6 +7,7 @@ */ #pragma once +#include #include #include @@ -24,9 +25,23 @@ struct socket_ops { int (*close)(int sd); }; +struct sd_event; +struct sd_event_source; +struct sd_event_ops { + int (*add_time_relative)(struct sd_event *e, + struct sd_event_source **ret, clockid_t clock, + uint64_t usec, uint64_t accuracy, + int (*callback)(struct sd_event_source *s, + uint64_t usec, void *userdata), + void *userdata); + int (*source_set_time_relative)(struct sd_event_source *s, + uint64_t usec); +}; + struct mctp_ops { struct socket_ops mctp; struct socket_ops nl; + struct sd_event_ops sd_event; void (*bug_warn)(const char *fmt, va_list args); }; diff --git a/src/mctpd.c b/src/mctpd.c index 36a23727..ede69bcb 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -126,7 +126,6 @@ enum discovery_state { }; struct link { - enum discovery_state discovered; bool published; int ifindex; enum endpoint_role role; @@ -135,6 +134,14 @@ struct link { sd_bus_slot *slot_iface; sd_bus_slot *slot_busowner; + struct { + enum discovery_state flag; + sd_event_source *notify_source; + dest_phys notify_dest; + uint64_t notify_retry_delay; + uint8_t notify_tries_left; + } discovery; + struct ctx *ctx; }; @@ -497,8 +504,9 @@ static int wait_fd_timeout(int fd, short events, uint64_t timeout_usec) if (rc < 0) goto out; - rc = sd_event_add_time_relative(ev, NULL, CLOCK_MONOTONIC, timeout_usec, - 0, cb_exit_loop_timeout, NULL); + rc = mctp_ops.sd_event.add_time_relative(ev, NULL, CLOCK_MONOTONIC, + timeout_usec, 0, + cb_exit_loop_timeout, NULL); if (rc < 0) goto out; @@ -804,8 +812,8 @@ static int handle_control_set_endpoint_id(struct ctx *ctx, int sd, warnx("ERR: cannot add bus owner to object lists"); } - if (link_data->discovered != DISCOVERY_UNSUPPORTED) { - link_data->discovered = DISCOVERY_DISCOVERED; + if (link_data->discovery.flag != DISCOVERY_UNSUPPORTED) { + link_data->discovery.flag = DISCOVERY_DISCOVERED; } resp->status = SET_MCTP_EID_ASSIGNMENT_STATUS(MCTP_SET_EID_ACCEPTED) | @@ -816,13 +824,13 @@ static int handle_control_set_endpoint_id(struct ctx *ctx, int sd, return reply_message(ctx, sd, resp, resp_len, addr); case MCTP_SET_EID_DISCOVERED: - if (link_data->discovered == DISCOVERY_UNSUPPORTED) { + if (link_data->discovery.flag == DISCOVERY_UNSUPPORTED) { resp->completion_code = MCTP_CTRL_CC_ERROR_INVALID_DATA; resp_len = sizeof(struct mctp_ctrl_resp); return reply_message(ctx, sd, resp, resp_len, addr); } - link_data->discovered = DISCOVERY_DISCOVERED; + link_data->discovery.flag = DISCOVERY_DISCOVERED; resp->status = SET_MCTP_EID_ASSIGNMENT_STATUS(MCTP_SET_EID_REJECTED) | SET_MCTP_EID_ALLOCATION_STATUS(MCTP_SET_EID_POOL_NONE); @@ -1060,7 +1068,7 @@ static int handle_control_prepare_endpoint_discovery( resp = (void *)resp; mctp_ctrl_msg_hdr_init_resp(&resp->ctrl_hdr, *req); - if (link_data->discovered == DISCOVERY_UNSUPPORTED) { + if (link_data->discovery.flag == DISCOVERY_UNSUPPORTED) { warnx("received prepare for discovery request to unsupported interface %d", addr->smctp_ifindex); resp->completion_code = MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD; @@ -1068,8 +1076,8 @@ static int handle_control_prepare_endpoint_discovery( sizeof(struct mctp_ctrl_resp), addr); } - if (link_data->discovered == DISCOVERY_DISCOVERED) { - link_data->discovered = DISCOVERY_UNDISCOVERED; + if (link_data->discovery.flag == DISCOVERY_DISCOVERED) { + link_data->discovery.flag = DISCOVERY_UNDISCOVERED; warnx("clear discovered flag of interface %d", addr->smctp_ifindex); } @@ -1104,13 +1112,13 @@ handle_control_endpoint_discovery(struct ctx *ctx, int sd, return 0; } - if (link_data->discovered == DISCOVERY_UNSUPPORTED) { + if (link_data->discovery.flag == DISCOVERY_UNSUPPORTED) { resp->completion_code = MCTP_CTRL_CC_ERROR_INVALID_DATA; return reply_message(ctx, sd, resp, sizeof(struct mctp_ctrl_resp), addr); } - if (link_data->discovered == DISCOVERY_DISCOVERED) { + if (link_data->discovery.flag == DISCOVERY_DISCOVERED) { // if we are already discovered (i.e, assigned an EID), then no reply return 0; } @@ -3239,8 +3247,8 @@ static int peer_endpoint_recover(sd_event_source *s, uint64_t usec, reschedule: if (peer->recovery.npolls > 0) { - rc = sd_event_source_set_time_relative(peer->recovery.source, - peer->recovery.delay); + rc = mctp_ops.sd_event.source_set_time_relative( + peer->recovery.source, peer->recovery.delay); if (rc >= 0) { rc = sd_event_source_set_enabled(peer->recovery.source, SD_EVENT_ONESHOT); @@ -3275,7 +3283,7 @@ static int method_endpoint_recover(sd_bus_message *call, void *data, peer->recovery.npolls = MCTP_I2C_TSYM_MN1_MIN + 1; peer->recovery.delay = (MCTP_I2C_TSYM_TRECLAIM_MIN_US / 2) - ctx->mctp_timeout; - rc = sd_event_add_time_relative( + rc = mctp_ops.sd_event.add_time_relative( ctx->event, &peer->recovery.source, CLOCK_MONOTONIC, 0, ctx->mctp_timeout, peer_endpoint_recover, peer); if (rc < 0) { @@ -3658,6 +3666,88 @@ static int bus_link_get_prop(sd_bus *bus, const char *path, return rc; } +static int query_discovery_notify(struct link *link) +{ + struct mctp_ctrl_cmd_discovery_notify req = { 0 }; + struct mctp_ctrl_resp_discovery_notify *resp; + struct sockaddr_mctp_ext resp_addr; + size_t buf_size; + uint8_t *buf; + int rc; + + mctp_ctrl_msg_hdr_init_req(&req.ctrl_hdr, mctp_next_iid(link->ctx), + MCTP_CTRL_CMD_DISCOVERY_NOTIFY); + + rc = endpoint_query_phys(link->ctx, &link->discovery.notify_dest, + MCTP_CTRL_HDR_MSG_TYPE, &req, sizeof(req), + &buf, &buf_size, &resp_addr); + if (rc < 0) + goto free_buf; + + if (buf_size != sizeof(*resp)) { + warnx("%s: wrong reply length %zu bytes. dest %s", __func__, + buf_size, dest_phys_tostr(&link->discovery.notify_dest)); + rc = -ENOMSG; + goto free_buf; + } + + resp = (void *)buf; + if (resp->completion_code != 0) { + warnx("Failure completion code 0x%02x from %s", + resp->completion_code, + dest_phys_tostr(&link->discovery.notify_dest)); + rc = -ECONNREFUSED; + goto free_buf; + } + +free_buf: + free(buf); + return rc; +} + +static int link_discovery_notify_callback(sd_event_source *source, + uint64_t time, void *userdata) +{ + struct link *link = userdata; + struct ctx *ctx = link->ctx; + int rc; + + // sanity check + assert(link->discovery.notify_source == source); + + // Discovery notify succeeded + if (link->discovery.flag == DISCOVERY_DISCOVERED) + goto disarm; + + rc = query_discovery_notify(link); + if (rc < 0) { + if (ctx->verbose) { + warnx("failed to send discovery notify at retry %d: %s", + link->discovery.notify_tries_left, strerror(-rc)); + } + } + + link->discovery.notify_tries_left -= 1; + if (link->discovery.notify_tries_left == 0) { + warnx("failed to send discovery notify after all retries"); + goto disarm; + } + + rc = mctp_ops.sd_event.source_set_time_relative( + source, link->discovery.notify_retry_delay); + if (rc < 0) { + warnx("failed to rearm discovery notify timer"); + goto disarm; + } + + return 0; + +disarm: + sd_event_source_disable_unref(source); + link->discovery.notify_source = NULL; + return 0; +} + static int bus_link_set_prop(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, @@ -4495,7 +4585,7 @@ static int add_interface(struct ctx *ctx, int ifindex) if (!link) return -ENOMEM; - link->discovered = DISCOVERY_UNSUPPORTED; + link->discovery.flag = DISCOVERY_UNSUPPORTED; link->published = false; link->ifindex = ifindex; link->ctx = ctx; @@ -4525,7 +4615,42 @@ static int add_interface(struct ctx *ctx, int ifindex) } if (phys_binding == MCTP_PHYS_BINDING_PCIE_VDM) { - link->discovered = DISCOVERY_UNDISCOVERED; + link->discovery.flag = DISCOVERY_UNDISCOVERED; + // TODO: These numbers are respectively MN1 and MT4, specified in DSP0239 + // control message timing. + // + // Might need to extract these to macros like MCTP_I2C_TSYM_* in this file, + // or a commit to actually centralize those timing at one place, now that + // we have support for detecting link binding type. + link->discovery.notify_tries_left = 3; + link->discovery.notify_retry_delay = 5000000; + + // For PCIe-VDM, we want an all zeroes address for Route-to-Root-Complex. + rc = mctp_nl_hwaddr_len_byindex( + ctx->nl, ifindex, + &link->discovery.notify_dest.hwaddr_len); + if (rc < 0) { + warnx("Can't find hwaddr_len by index %d", ifindex); + return -ENOENT; + } + + memset(link->discovery.notify_dest.hwaddr, 0, + link->discovery.notify_dest.hwaddr_len); + link->discovery.notify_dest.ifindex = ifindex; + + rc = mctp_ops.sd_event.add_time_relative( + ctx->event, &link->discovery.notify_source, + CLOCK_MONOTONIC, 0, 0, link_discovery_notify_callback, + link); + if (rc >= 0) { + rc = sd_event_source_set_enabled( + link->discovery.notify_source, SD_EVENT_ON); + } + if (rc < 0) { + warnx("Failed to arm discovery notify timer"); + sd_event_source_disable_unref( + link->discovery.notify_source); + } } link->published = true; diff --git a/tests/conftest.py b/tests/conftest.py index b87dc2b1..7fca8e81 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ import pytest import asyncdbus +import trio.testing import mctpenv @@ -35,3 +36,10 @@ async def mctpd(nursery, dbus, sysnet, config): @pytest.fixture async def mctp(nursery, sysnet): return mctpenv.MctpWrapper(nursery, sysnet) + +@pytest.fixture +def autojump_clock(): + """ + Custom autojump clock with a reasonable threshold for non-time I/O waits + """ + return trio.testing.MockClock(autojump_threshold=0.01) diff --git a/tests/mctp-ops-test.c b/tests/mctp-ops-test.c index 7b63cd4e..df3927da 100644 --- a/tests/mctp-ops-test.c +++ b/tests/mctp-ops-test.c @@ -7,6 +7,7 @@ #define _GNU_SOURCE +#include #include #include #include @@ -18,6 +19,9 @@ #include #include +#if HAVE_LIBSYSTEMD +#include +#endif #include #include "mctp-ops.h" @@ -38,10 +42,12 @@ static int mctp_op_socket(int type) struct iovec iov; int rc, var, sd; - if (type == AF_MCTP) + if (type == CONTROL_OP_SOCKET_MCTP) req.type = CONTROL_OP_SOCKET_MCTP; - else if (type == AF_NETLINK) + else if (type == CONTROL_OP_SOCKET_NL) req.type = CONTROL_OP_SOCKET_NL; + else if (type == CONTROL_OP_TIMER) + req.type = CONTROL_OP_TIMER; else errx(EXIT_FAILURE, "invalid socket type?"); @@ -72,12 +78,12 @@ static int mctp_op_socket(int type) static int mctp_op_mctp_socket(void) { - return mctp_op_socket(AF_MCTP); + return mctp_op_socket(CONTROL_OP_SOCKET_MCTP); } static int mctp_op_netlink_socket(void) { - return mctp_op_socket(AF_NETLINK); + return mctp_op_socket(CONTROL_OP_SOCKET_NL); } static int mctp_op_bind(int sd, struct sockaddr *addr, socklen_t addrlen) @@ -221,6 +227,115 @@ static void mctp_bug_warn(const char *fmt, va_list args) abort(); } +#if HAVE_LIBSYSTEMD +struct wrapped_time_userdata { + sd_event_time_handler_t callback; + void *userdata; +}; + +int wrapped_time_callback(sd_event_source *source, int fd, uint revents, + void *userdata) +{ + struct wrapped_time_userdata *wrapud = userdata; + uint64_t usec; + ssize_t rc; + + rc = read(fd, &usec, sizeof(usec)); + if (rc != 8) + errx(EXIT_FAILURE, "ops protocol error"); + + rc = wrapud->callback(source, usec, wrapud->userdata); + warnx("%ld", rc); + + return 0; +} + +void wrapped_time_destroy(void *wrapud) +{ + free(wrapud); +} + +static int mctp_op_sd_event_add_time_relative( + sd_event *e, sd_event_source **ret, clockid_t clock, uint64_t usec, + uint64_t accuracy, sd_event_time_handler_t callback, void *userdata) +{ + struct wrapped_time_userdata *wrapud = NULL; + sd_event_source *source = NULL; + int sd = -1; + int rc = 0; + + sd = mctp_op_socket(CONTROL_OP_TIMER); + if (sd < 0) + return -errno; + + rc = write(sd, &usec, sizeof(usec)); + if (rc != 8) + errx(EXIT_FAILURE, "ops protocol error"); + + wrapud = malloc(sizeof(*wrapud)); + if (!wrapud) { + rc = -ENOMEM; + goto fail; + } + + wrapud->callback = callback; + wrapud->userdata = userdata; + + rc = sd_event_add_io(e, &source, sd, EPOLLIN, wrapped_time_callback, + wrapud); + if (rc < 0) + goto fail; + + rc = sd_event_source_set_destroy_callback(source, wrapped_time_destroy); + if (rc < 0) + goto fail; + + wrapud = NULL; + + rc = sd_event_source_set_io_fd_own(source, 1); + if (rc < 0) + goto fail; + + sd = -1; + + rc = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT); + if (rc < 0) + goto fail; + + if (!ret) { + rc = sd_event_source_set_floating(source, 1); + if (rc < 0) + goto fail; + + sd_event_source_unref(source); + } else { + *ret = source; + } + + return 0; + +fail: + if (sd > 0) + close(sd); + free(wrapud); + sd_event_source_disable_unref(*ret); + return rc; +} + +static int mctp_op_sd_event_source_set_time_relative(sd_event_source *s, + uint64_t usec) +{ + int sd = sd_event_source_get_io_fd(s); + ssize_t rc; + + rc = write(sd, &usec, sizeof(usec)); + if (rc != 8) + errx(EXIT_FAILURE, "ops protocol error"); + + return 0; +} +#endif + const struct mctp_ops mctp_ops = { .mctp = { .socket = mctp_op_mctp_socket, @@ -238,6 +353,12 @@ const struct mctp_ops mctp_ops = { .recvfrom = mctp_op_recvfrom, .close = mctp_op_close, }, +#if HAVE_LIBSYSTEMD + .sd_event = { + .add_time_relative = mctp_op_sd_event_add_time_relative, + .source_set_time_relative = mctp_op_sd_event_source_set_time_relative, + }, +#endif .bug_warn = mctp_bug_warn, }; diff --git a/tests/mctpenv/__init__.py b/tests/mctpenv/__init__.py index d1fc6714..9b4ad990 100644 --- a/tests/mctpenv/__init__.py +++ b/tests/mctpenv/__init__.py @@ -2,6 +2,7 @@ import array import enum import errno +import math import os import signal import socket @@ -1113,6 +1114,32 @@ async def notify_delroute(self, route): await self._notify_route(route, rtnl.RTM_DELROUTE); +class TimerSocket(BaseSocket): + def __init__(self, sock): + super().__init__(sock) + self.delay = sys.maxsize + + async def run(self): + while True: + try: + with trio.move_on_after(self.delay / 1000000) as scope: + # now = math.floor(trio.current_time() * 1000000) + # await self.sock.send(struct.pack('@Q', now)) + data = await self.sock.recv(8) + if len(data) == 0: + break + + (next_delay,) = struct.unpack('@Q', data) + self.delay = next_delay + + # timed out + if scope.cancelled_caught: + await self.sock.send(struct.pack('@Q', math.floor(trio.current_time() * 1000000))) + self.delay = sys.maxsize + except (ConnectionResetError, BrokenPipeError) as ex: + break + + async def send_fd(sock, fd): fdarray = array.array("i", [fd]) await sock.sendmsg([b'x'], [ @@ -1158,6 +1185,14 @@ async def handle_control(self, nursery): remote.close() nursery.start_soon(nl.run) + elif op == 0x03: + # Timer socket + (local, remote) = self.socketpair() + sd = TimerSocket(local) + await send_fd(self.sock_local, remote.fileno()) + remote.close() + nursery.start_soon(sd.run) + else: print(f"unknown op {op}") diff --git a/tests/test-proto.h b/tests/test-proto.h index db3e8454..89c2f4d9 100644 --- a/tests/test-proto.h +++ b/tests/test-proto.h @@ -9,6 +9,7 @@ enum { CONTROL_OP_INIT, CONTROL_OP_SOCKET_MCTP, CONTROL_OP_SOCKET_NL, + CONTROL_OP_TIMER, }; struct control_msg_req { diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index ffb66944..8113df36 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -50,6 +50,7 @@ async def _introspect_path_recursive(dbus, path, node_set): return dups + """ Test that the dbus object tree is sensible: we can introspect all objects, and that there are no duplicates """ @@ -187,7 +188,7 @@ def ep_connectivity_changed(iface, changed, invalidated): # to transition 'Connectivity' to 'Available', which is a test failure. assert not expected.cancelled_caught -async def test_recover_endpoint_removed(dbus, mctpd): +async def test_recover_endpoint_removed(dbus, mctpd, autojump_clock): iface = mctpd.system.interfaces[0] dev = mctpd.network.endpoints[0] mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P) @@ -224,7 +225,7 @@ def ep_removed(ep_path, interfaces): assert not expected.cancelled_caught -async def test_recover_endpoint_reset(dbus, mctpd): +async def test_recover_endpoint_reset(dbus, mctpd, autojump_clock): iface = mctpd.system.interfaces[0] dev = mctpd.network.endpoints[0] mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P) @@ -260,7 +261,7 @@ def ep_connectivity_changed(iface, changed, invalidated): assert not expected.cancelled_caught -async def test_recover_endpoint_exchange(dbus, mctpd): +async def test_recover_endpoint_exchange(dbus, mctpd, autojump_clock): iface = mctpd.system.interfaces[0] dev = mctpd.network.endpoints[0] mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P) @@ -628,7 +629,7 @@ async def test_network_local_eids_none(dbus, mctpd): assert eids == [] -async def test_concurrent_recovery_setup(dbus, mctpd): +async def test_concurrent_recovery_setup(dbus, mctpd, autojump_clock): iface = mctpd.system.interfaces[0] mctp_i = await mctpd_mctp_iface_obj(dbus, iface) diff --git a/tests/test_mctpd_endpoint.py b/tests/test_mctpd_endpoint.py index 82ecf28c..00f7d9f2 100644 --- a/tests/test_mctpd_endpoint.py +++ b/tests/test_mctpd_endpoint.py @@ -22,11 +22,16 @@ async def iface(): @pytest.fixture -async def sysnet(iface): +async def bo(iface): + return Endpoint(iface, bytes([0x10]), eid=8) + + +@pytest.fixture +async def sysnet(iface, bo): system = System() await system.add_interface(iface) network = Network() - network.add_endpoint(Endpoint(iface, bytes([0x10]), eid=8)) + network.add_endpoint(bo) return Sysnet(system, network) @@ -113,12 +118,100 @@ class TestDiscovery: async def iface(self): return System.Interface("mctp0", 1, 1, bytes([0x1D]), 68, 254, True, PhysicalBinding.PCIE_VDM) + @pytest.fixture + async def bo(self, iface): + return TestDiscovery.BusOwnerEndpoint(iface, bytes([0x00]), eid=8) + + + class BusOwnerEndpoint(Endpoint): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.sem = trio.Semaphore(initial_value=0) + + async def handle_mctp_control(self, sock, addr, data): + print(addr, data) + flags, opcode = data[0:2] + if opcode != 0x0D: + return await super().handle_mctp_control(sock, addr, data) + dst_addr = MCTPSockAddr.for_ep_resp(self, addr, sock.addr_ext) + await sock.send(dst_addr, bytes([flags & 0x1F, opcode, 0x00])) + self.sem.release() + + """ Test simple Discovery sequence """ async def test_simple_discovery_sequence(self, dbus, mctpd): bo = mctpd.network.endpoints[0] assert len(mctpd.system.addresses) == 0 + # BMC should send a Discovery Notify message + with trio.move_on_after(5) as expected: + await bo.sem.acquire() + assert not expected.cancelled_caught + + # no EID yet + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x02)) + assert rsp.hex(' ') == '00 02 00 00 02 00' + + # BMC response to Prepare for Discovery + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x0B)) + assert rsp.hex(' ') == '00 0b 00' + + # BMC response to Endpoint Discovery + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x0C)) + assert rsp.hex(' ') == '00 0c 00' + + # set EID = 42 + eid = 42 + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x01, bytes([0x00, eid]))) + assert rsp.hex(' ') == f'00 01 00 00 {eid:02x} 00' + + # BMC should contains two object paths: bus owner and itself + assert await mctpd_mctp_endpoint_control_obj(dbus, f"/au/com/codeconstruct/mctp1/networks/1/endpoints/{bo.eid}") + assert await mctpd_mctp_endpoint_control_obj(dbus, f"/au/com/codeconstruct/mctp1/networks/1/endpoints/{eid}") + + +class TestDiscoveryRetry: + @pytest.fixture + async def iface(self): + return System.Interface("mctp0", 1, 1, bytes([0x1D]), 68, 254, True, PhysicalBinding.PCIE_VDM) + + @pytest.fixture + async def bo(self, iface): + return TestDiscoveryRetry.BusOwnerEndpoint(iface, bytes([0x00]), eid=8) + + + class BusOwnerEndpoint(Endpoint): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.sem = trio.Semaphore(initial_value=0) + self.retry_left = 1 + + async def handle_mctp_control(self, sock, src_addr, msg): + flags, opcode = msg[0:2] + if opcode != 0x0D: + return await super().handle_mctp_control(sock, src_addr, msg) + + # only reply after 2 retries + if self.retry_left == 0: + dst_addr = MCTPSockAddr.for_ep_resp(self, src_addr, sock.addr_ext) + await sock.send(dst_addr, bytes([flags & 0x1F, opcode, 0x00])) + self.sem.release() + else: + self.retry_left -= 1 + + + """ Test simple Discovery sequence """ + async def test_discovery_after_one_retry(self, dbus, mctpd, autojump_clock): + bo = mctpd.network.endpoints[0] + + assert len(mctpd.system.addresses) == 0 + + # BMC should send a Discovery Notify message + with trio.move_on_after(10) as expected: + await bo.sem.acquire() + assert not expected.cancelled_caught + # no EID yet rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x02)) assert rsp.hex(' ') == '00 02 00 00 02 00'