Extend the multi-frame tests with the transport edge cases the round-trip tests do not reach: a per-CTS block limit smaller than the message, the RTS/CTS and rx timeouts that must end a stalled session with a Connection Abort, and a received Connection Abort that cancels an incoming session.
Assisted-by: LLM Signed-off-by: Oleksij Rempel <[email protected]> --- .../selftests/net/can/test_j1939_multi.c | 187 +++++++++++++++++- 1 file changed, 186 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/can/test_j1939_multi.c b/tools/testing/selftests/net/can/test_j1939_multi.c index 1f27ee58f48c..069a9c8690b5 100644 --- a/tools/testing/selftests/net/can/test_j1939_multi.c +++ b/tools/testing/selftests/net/can/test_j1939_multi.c @@ -5,7 +5,18 @@ * A CAN_RAW socket plays the transport peer and drives the full handshake * against the kernel J1939 stack over the wire, in both directions: for TP the * raw side answers RTS/CTS and exchanges DT frames with EOMA; ETP does the same - * with a payload spanning several CTS blocks (DPO windows). + * with a payload spanning several CTS blocks (DPO windows). The tests also + * cover the CTS block limit and the abort and timeout paths. Those edge cases + * are TP only; ETP has no equivalents here yet. + * + * Timing limitation: the abort/timeout tests wait with wide receive windows + * rather than tight bounds, because timing over vcan under QEMU is not tightly + * bounded. They catch a gross timer regression (e.g. a doubled timeout) but not + * a subtle shift; see the per-test comments. + * + * Coverage limitation: a received Connection Abort is exercised RX-side only; + * there is no kernel-as-transmitter received-abort test, matching the known + * net-next limitation that a TX session ignores an incoming Connection Abort. * * The vcan interface is set up by the caller (see test_j1939_multi.sh); its name * is passed via the CANIF environment variable. @@ -39,6 +50,14 @@ #define ETP_CMD_DPO 0x16 #define ETP_CMD_EOMA 0x17 +/* TP.Abort reason codes (not durations), from the kernel's enum j1939_xtp_abort */ +#define ABORT_REASON_TIMEOUT 3 /* J1939_XTP_ABORT_TIMEOUT */ +#define ABORT_REASON_BUSY 1 /* J1939_XTP_ABORT_BUSY */ + +/* TP responder T2: wait for the next DT after CTS (ISO 11783-3:1998 §3.10.2.4). */ +#define TP_T2_MS 1250 +#define TP_RX_ABORT_WAIT_MS (TP_T2_MS * 2) /* generous; floor check is the real test */ + #define TP_SIZE 200 #define ETP_SIZE 6000 /* > TP max 1785 (255*7); several blocks */ @@ -313,6 +332,69 @@ TEST_F(j1939_multi, tp_tx_to_raw) EXPECT_EQ(memcmp(payload, rx_buf, sizeof(payload)), 0); } +/* + * TP RX, per-CTS block limit: the RTS advertises a "max packets per CTS" + * (RTS data byte 4, 0-based; the spec's byte 5) smaller than the total packet + * count. The kernel must never request more than that many packets in a + * single CTS. + */ +TEST_F(j1939_multi, tp_rx_cts_block_limit) +{ + const unsigned int limit = 3; + unsigned int num_pkts = J1939_NUM_PKTS(TP_SIZE); + struct can_frame frame; + int max_want; + + ASSERT_GT(num_pkts, limit) + TH_LOG("test needs more packets than the CTS limit"); + + /* advertise a max packets/CTS (limit) smaller than the total */ + ASSERT_EQ(tp_rx_roundtrip(self, limit, &max_want, &frame), 0) + TH_LOG("TP RX round-trip against the raw peer failed"); + + EXPECT_LE(max_want, (int)limit) + TH_LOG("kernel CTS requested %d packets, sender limit was %u", + max_want, limit); +} + +/* + * TP TX, RTS->CTS timeout: the J1939 socket starts a TP transmit; the raw peer + * receives the RTS but never answers with a CTS. The kernel must give up (spec + * T3 = 1250 ms, SAE J1939-21:2001 §5.12.3) and send a Connection Abort. + * + * The abort must arrive within 2000 ms (a doubled T3 would miss this window) but + * no sooner than 800 ms, safely below the 1250 ms timer, so an instant-fire + * regression (an abort emitted immediately on a zeroed timer) is caught too. The + * bounds are wide, not tight, to stay reliable over vcan, so a subtle shift + * (e.g. 1250->1800 ms) still escapes. + */ +TEST_F(j1939_multi, tp_tx_rts_cts_timeout) +{ + uint8_t payload[TP_SIZE]; + struct timespec t0, t1; + struct can_frame frame; + long elapsed_ms; + + fill_pattern(payload, sizeof(payload)); + + ASSERT_EQ(tx_start_session(&self->j1939, self->ifindex, payload, + sizeof(payload), self->raw, J1939_PF(TP_CTL), + TP_CMD_RTS, &frame), 0); + + /* Deliberately send no CTS; expect the kernel to abort within 2000 ms. */ + clock_gettime(CLOCK_MONOTONIC, &t0); + ASSERT_EQ(recv_ctl_cmd(self->raw, J1939_PF(TP_CTL), SA_PEER, &frame, + 2000, TP_CMD_ABORT), 0) + TH_LOG("kernel did not send a Connection Abort within 2000 ms"); + clock_gettime(CLOCK_MONOTONIC, &t1); + elapsed_ms = timespec_diff_ms(&t0, &t1); + /* must not fire instantly: the T3 timer has to run before the abort */ + ASSERT_GE(elapsed_ms, 800) + TH_LOG("abort fired after only %ld ms, timer did not run", elapsed_ms); + /* the T3 expiry must be reported as a timeout abort */ + EXPECT_EQ(frame.data[1], ABORT_REASON_TIMEOUT); +} + /* * ETP RX: the CAN_RAW peer transmits an ETP message to the J1939 socket. * The kernel requests blocks with ETP.CTS; raw answers each with a DPO and @@ -450,4 +532,107 @@ TEST_F(j1939_multi, etp_tx_to_raw) free(rx_buf); } +/* Send a TP.CM_Abort control frame. */ +static int send_abort(int raw, uint8_t da, uint8_t sa, uint8_t reason) +{ + uint8_t dat[8]; + + ctl_dat_init(dat, TP_CMD_ABORT); + dat[1] = reason; + return send_ctl_frame(raw, PRIO, TP_CTL, da, sa, dat, PGN_DATA); +} + +/* Open a TP RX j1939 socket, send a full RTS, consume the first CTS. */ +static int tp_rx_start_session(int *j1939, int raw, int ifindex, + unsigned int num_pkts, struct can_frame *cts) +{ + if (tp_rx_open_and_send_rts(j1939, raw, ifindex, num_pkts, + num_pkts /* no per-CTS cap */)) + return -1; + return recv_ctl_cmd(raw, J1939_PF(TP_CTL), SA_PEER, cts, + J1939_RECV_TIMEOUT_MS, TP_CMD_CTS); +} + +/* + * TP RX timeout: the CAN_RAW peer opens a session with RTS but never sends the + * data; the kernel's rx timer must abort the session and emit a TP.CM_Abort. + * The wait uses wide bounds (not a tight window) to stay reliable over vcan: the + * lower bound catches an instant-fire regression; the upper bound is a generous + * T2 multiple, so timer shifts within it escape. + */ +TEST_F(j1939_multi, tp_rx_timeout) +{ + unsigned int num_pkts = J1939_NUM_PKTS(TP_SIZE); + struct timespec t0, t1; + struct can_frame frame; + long elapsed_ms; + + ASSERT_EQ(tp_rx_start_session(&self->j1939, self->raw, self->ifindex, + num_pkts, &frame), 0) + TH_LOG("failed to open and start the TP rx session"); + + /* + * No data follows the CTS; the kernel's rx timer (T2, see TP_T2_MS) must + * fire and abort; the wait is a generous TP_T2_MS * 2, the floor below is + * the real check. + */ + clock_gettime(CLOCK_MONOTONIC, &t0); + ASSERT_EQ(recv_ctl_cmd(self->raw, J1939_PF(TP_CTL), SA_PEER, &frame, + TP_RX_ABORT_WAIT_MS, TP_CMD_ABORT), 0) + TH_LOG("kernel did not abort the stalled rx session"); + clock_gettime(CLOCK_MONOTONIC, &t1); + elapsed_ms = timespec_diff_ms(&t0, &t1); + /* + * Must not fire instantly: the T2 timer (1250 ms) has to run before the + * abort, so 800 ms is a floor safely below it. + */ + ASSERT_GE(elapsed_ms, 800) + TH_LOG("abort fired after only %ld ms, timer did not run", elapsed_ms); + EXPECT_EQ(frame.data[1], ABORT_REASON_TIMEOUT); +} + +/* + * TP RX abort: the CAN_RAW peer aborts an open session; the kernel must cancel + * it and deliver nothing to the J1939 socket. + */ +TEST_F(j1939_multi, tp_rx_abort_from_raw) +{ + uint8_t payload[TP_SIZE]; + unsigned int num_pkts = J1939_NUM_PKTS(TP_SIZE); + struct can_frame frame; + struct pollfd pfd; + unsigned int i; + + fill_pattern(payload, sizeof(payload)); + + ASSERT_EQ(tp_rx_start_session(&self->j1939, self->raw, self->ifindex, + num_pkts, &frame), 0) + TH_LOG("failed to open and start the TP rx session"); + + ASSERT_EQ(send_abort(self->raw, SA_LOCAL, SA_PEER, ABORT_REASON_BUSY), 0); + /* let the kernel process the abort first; 100 ms is a generous + * empirical margin, not a spec value. + */ + usleep(100000); + + /* + * Send the full DT burst anyway. A kernel that honoured the abort has + * torn the session down and drops these; one that ignored it would + * reassemble them and deliver, so POLLIN below means the abort was not + * honoured. + */ + for (i = 1; i <= num_pkts; i++) + ASSERT_EQ(send_dt(self->raw, PRIO, TP_DAT, SA_LOCAL, SA_PEER, + i, payload, (i - 1) * 7, TP_SIZE), 0); + + pfd.fd = self->j1939; + pfd.events = POLLIN; + /* + * Short negative-check window: nothing should arrive after the abort, + * so this is deliberately shorter than J1939_RECV_TIMEOUT_MS. + */ + EXPECT_EQ(poll(&pfd, 1, J1939_NEG_RECV_TIMEOUT_MS), 0) + TH_LOG("socket delivered data despite the abort"); +} + J1939_TEST_MAIN(); -- 2.47.3

