Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .fusa-reqs-pending.json

Large diffs are not rendered by default.

460 changes: 460 additions & 0 deletions .fusa-reqs.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions include/rcp/e2e.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
// fusa:req REQ-E2E-021
// fusa:req REQ-E2E-028
// fusa:req REQ-E2E-029
// fusa:req REQ-E2E-030
// fusa:req REQ-E2E-032
// fusa:req REQ-E2E-035
// fusa:req REQ-E2E-036
// fusa:req REQ-E2E-037
// fusa:req REQ-E2E-038
// fusa:req REQ-E2E-040
// fusa:req REQ-E2E-042
// fusa:req REQ-E2E-045
// fusa:req REQ-E2E-046

Expand Down
19 changes: 18 additions & 1 deletion include/rcp/lifecycle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,32 @@
// fusa:req REQ-LIFECYCLE-005
// fusa:req REQ-LIFECYCLE-006
// fusa:req REQ-LIFECYCLE-007
// fusa:req REQ-LIFECYCLE-013
// fusa:req REQ-LIFECYCLE-014
// fusa:req REQ-LIFECYCLE-015
// fusa:req REQ-LIFECYCLE-016
// fusa:req REQ-LIFECYCLE-017
// fusa:req REQ-LIFECYCLE-018
// fusa:req REQ-LIFECYCLE-019
// fusa:req REQ-LIFECYCLE-020
// fusa:req REQ-LIFECYCLE-021
// fusa:req REQ-LIFECYCLE-022
// fusa:req REQ-LIFECYCLE-023
// fusa:req REQ-LIFECYCLE-024
// fusa:req REQ-LIFECYCLE-025
// fusa:req REQ-LIFECYCLE-026
// fusa:req REQ-LIFECYCLE-027
// fusa:req REQ-LIFECYCLE-028
// fusa:req REQ-LIFECYCLE-029
// fusa:req REQ-LIFECYCLE-030
// fusa:req REQ-LIFECYCLE-031
// fusa:req REQ-LIFECYCLE-032
// fusa:req REQ-LIFECYCLE-033
// fusa:req REQ-LIFECYCLE-034
// fusa:req REQ-LIFECYCLE-035
// fusa:req REQ-LIFECYCLE-036
// fusa:req REQ-LIFECYCLE-037
// fusa:req REQ-LIFECYCLE-038
// fusa:req REQ-LIFECYCLE-039
// fusa:req REQ-RMAP-049
// fusa:req REQ-RMAP-055

Expand Down
20 changes: 14 additions & 6 deletions tests/test_e2e.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
// fusa:test REQ-E2E-021
// fusa:test REQ-E2E-028
// fusa:test REQ-E2E-029
// fusa:test REQ-E2E-030
// fusa:test REQ-E2E-032
// fusa:test REQ-E2E-035
// fusa:test REQ-E2E-036
// fusa:test REQ-E2E-037
// fusa:test REQ-E2E-038
// fusa:test REQ-E2E-040
// fusa:test REQ-E2E-042
// fusa:test REQ-E2E-045
// fusa:test REQ-E2E-046

Expand Down Expand Up @@ -309,7 +315,7 @@ TEST_CASE("length_with_crc adds exactly kCrcLengthAdjustOctets and saturates on

TEST_CASE("data_length_for_protected_members multiplies by kCrcLengthAdjustOctets and saturates "
"on overflow",
"[e2e][REQ-E2E-004]") {
"[e2e][REQ-E2E-037]") {
REQUIRE(data_length_for_protected_members(0) == 0);
REQUIRE(data_length_for_protected_members(3) == 12);
REQUIRE(data_length_for_protected_members(static_cast<size_t>(-1)) == static_cast<size_t>(-1)); // saturates
Expand Down Expand Up @@ -568,7 +574,8 @@ TEST_CASE("append_crc appends exactly 4 big-endian octets", "[e2e][REQ-E2E-004]"

// ── wrap / unwrap (c-RCP issue #420) ──────────────────────────────────────────

TEST_CASE("wrap/unwrap round trips an unpadded ACF_ABB payload", "[e2e][REQ-E2E-005][REQ-E2E-006]") {
TEST_CASE("wrap/unwrap round trips an unpadded ACF_ABB payload",
"[e2e][REQ-E2E-005][REQ-E2E-006][REQ-E2E-032][REQ-E2E-036][REQ-E2E-040]") {
auto sid = make_stream_id(0x02, 0x0010);
AcfMessageInfo info;
info.byte_bus_id = 11;
Expand All @@ -588,7 +595,8 @@ TEST_CASE("wrap/unwrap round trips an unpadded ACF_ABB payload", "[e2e][REQ-E2E-
REQUIRE(result.acf_frame == rcp::acf::encode_acf_abb(original, payload));
}

TEST_CASE("wrap places the CRC before trailing pad octets, not after", "[e2e][REQ-E2E-005][REQ-E2E-006]") {
TEST_CASE("wrap places the CRC before trailing pad octets, not after",
"[e2e][REQ-E2E-005][REQ-E2E-006][REQ-E2E-042]") {
auto sid = make_stream_id(0x02, 0x0011);
AcfMessageInfo info;
info.byte_bus_id = 12;
Expand All @@ -610,7 +618,7 @@ TEST_CASE("wrap places the CRC before trailing pad octets, not after", "[e2e][RE
}

TEST_CASE("wrap/unwrap round trips a padded ACF_ABB payload with the pad re-seated after the CRC",
"[e2e][REQ-E2E-005][REQ-E2E-006]") {
"[e2e][REQ-E2E-005][REQ-E2E-006][REQ-E2E-042]") {
auto sid = make_stream_id(0x02, 0x0012);
AcfMessageInfo info;
info.byte_bus_id = 13;
Expand Down Expand Up @@ -741,7 +749,7 @@ TEST_CASE("wrap_framed/unwrap_framed force the NTSCF zero-timestamp/false-tu sta

// ── Fragmentation/CRC interaction ─────────────────────────────────────────────

TEST_CASE("fragment_carries_crc is true only for the last fragment", "[e2e][REQ-E2E-010]") {
TEST_CASE("fragment_carries_crc is true only for the last fragment", "[e2e][REQ-E2E-038]") {
REQUIRE_FALSE(fragment_carries_crc(/*is_last_fragment=*/false));
REQUIRE(fragment_carries_crc(/*is_last_fragment=*/true));
}
Expand Down Expand Up @@ -1195,7 +1203,7 @@ TEST_CASE("apply_queue_overflow implements the same purge-normal/retain-safety r
}

TEST_CASE("overflow_should_enter_safe_state mirrors rx_ovrflw_safestate_enable directly",
"[e2e][REQ-E2E-010]") {
"[e2e][REQ-E2E-030]") {
REQUIRE_FALSE(overflow_should_enter_safe_state(false));
REQUIRE(overflow_should_enter_safe_state(true));
}
Expand Down
76 changes: 64 additions & 12 deletions tests/test_lifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,32 @@
// fusa:test REQ-LIFECYCLE-005
// fusa:test REQ-LIFECYCLE-006
// fusa:test REQ-LIFECYCLE-007
// fusa:test REQ-LIFECYCLE-013
// fusa:test REQ-LIFECYCLE-014
// fusa:test REQ-LIFECYCLE-015
// fusa:test REQ-LIFECYCLE-016
// fusa:test REQ-LIFECYCLE-017
// fusa:test REQ-LIFECYCLE-018
// fusa:test REQ-LIFECYCLE-019
// fusa:test REQ-LIFECYCLE-020
// fusa:test REQ-LIFECYCLE-021
// fusa:test REQ-LIFECYCLE-022
// fusa:test REQ-LIFECYCLE-023
// fusa:test REQ-LIFECYCLE-024
// fusa:test REQ-LIFECYCLE-025
// fusa:test REQ-LIFECYCLE-026
// fusa:test REQ-LIFECYCLE-027
// fusa:test REQ-LIFECYCLE-028
// fusa:test REQ-LIFECYCLE-029
// fusa:test REQ-LIFECYCLE-030
// fusa:test REQ-LIFECYCLE-031
// fusa:test REQ-LIFECYCLE-032
// fusa:test REQ-LIFECYCLE-033
// fusa:test REQ-LIFECYCLE-034
// fusa:test REQ-LIFECYCLE-035
// fusa:test REQ-LIFECYCLE-036
// fusa:test REQ-LIFECYCLE-037
// fusa:test REQ-LIFECYCLE-038
// fusa:test REQ-LIFECYCLE-039
// fusa:test REQ-RMAP-049
// fusa:test REQ-RMAP-055

Expand Down Expand Up @@ -360,7 +377,7 @@ TEST_CASE("check_rcp_cfg: a fully-consistent snapshot passes", "[lifecycle][REQ-
// advance() above — see transition()'s own doc comment for why.

TEST_CASE("transition: same-state is always a no-op success, unlike advance()",
"[lifecycle][REQ-LIFECYCLE-002]") {
"[lifecycle][REQ-LIFECYCLE-002][REQ-LIFECYCLE-013]") {
ServerLifecycle lc;
PlausibilitySnapshot snap;
WriterCtx writer; // everything false — deliberately unauthorized/non-idle
Expand Down Expand Up @@ -424,7 +441,7 @@ TEST_CASE("transition: HwConfigured -> RcpConfigured is also guarded by check_rc

TEST_CASE("transition: RcpConfigured -> HwConfigured demotion requires root client or valid "
"stream association, NOT the discovery stream alone",
"[lifecycle][REQ-LIFECYCLE-002][REQ-LIFECYCLE-037]") {
"[lifecycle][REQ-LIFECYCLE-002][REQ-LIFECYCLE-022][REQ-LIFECYCLE-037][REQ-LIFECYCLE-039]") {
ServerLifecycle lc;
PlausibilitySnapshot snap;
WriterCtx via_root;
Expand Down Expand Up @@ -471,7 +488,7 @@ TEST_CASE("transition: HwConfigured -> HwUnconfigured reset accepts discovery-st

TEST_CASE("transition: RcpConfigured -> HwUnconfigured reset requires the root client ALONE — "
"discovery stream is no longer sufficient",
"[lifecycle][REQ-LIFECYCLE-002][REQ-LIFECYCLE-037]") {
"[lifecycle][REQ-LIFECYCLE-002][REQ-LIFECYCLE-022][REQ-LIFECYCLE-037]") {
ServerLifecycle lc;
PlausibilitySnapshot snap;
WriterCtx via_root;
Expand Down Expand Up @@ -546,7 +563,7 @@ TEST_CASE("should_accept: a TSCF frame is dropped when time sync is unsupported

TEST_CASE("should_accept: HwUnconfigured drops TSCF outright and only accepts ACF_ABB on the "
"discovery byte_bus_id via NTSCF",
"[lifecycle][REQ-LIFECYCLE-014][REQ-LIFECYCLE-033]") {
"[lifecycle][REQ-LIFECYCLE-014][REQ-LIFECYCLE-015][REQ-LIFECYCLE-016][REQ-LIFECYCLE-033]") {
// TSCF is dropped even with time sync supported.
REQUIRE(should_accept(ServerState::HwUnconfigured, true, rcp::avtp::kSubtypeTscf,
rcp::acf::kAcfMsgTypeAbb, kDiscoveryByteBusId,
Expand All @@ -570,7 +587,8 @@ TEST_CASE("should_accept: HwUnconfigured drops TSCF outright and only accepts AC

TEST_CASE("should_accept: HwConfigured drops TSCF and non-EP0 traffic, accepts EP0 ACF_ABB, "
"rejects EP0 ACF_GBB",
"[lifecycle][REQ-LIFECYCLE-014][REQ-LIFECYCLE-033]") {
"[lifecycle][REQ-LIFECYCLE-014][REQ-LIFECYCLE-028][REQ-LIFECYCLE-029][REQ-LIFECYCLE-032]"
"[REQ-LIFECYCLE-033][REQ-LIFECYCLE-034]") {
REQUIRE(should_accept(ServerState::HwConfigured, true, rcp::avtp::kSubtypeTscf,
rcp::acf::kAcfMsgTypeAbb, kDiscoveryByteBusId,
rcp::avtp::TscfFallback::Drop) == Disposition::Drop);
Expand All @@ -589,7 +607,7 @@ TEST_CASE("should_accept: HwConfigured drops TSCF and non-EP0 traffic, accepts E
}

TEST_CASE("should_accept: RcpConfigured accepts everything beyond the general time-sync rule",
"[lifecycle][REQ-LIFECYCLE-014]") {
"[lifecycle][REQ-LIFECYCLE-014][REQ-LIFECYCLE-017][REQ-LIFECYCLE-025]") {
REQUIRE(should_accept(ServerState::RcpConfigured, true, rcp::avtp::kSubtypeNtscf,
rcp::acf::kAcfMsgTypeGbb, /*byte_bus_id=*/42,
rcp::avtp::TscfFallback::Drop) == Disposition::Accept);
Expand All @@ -601,7 +619,7 @@ TEST_CASE("should_accept: RcpConfigured accepts everything beyond the general ti
// ── field_writable / field_write_error — register-locking-by-state ──────────────

TEST_CASE("field_writable: HwGeneric is writable only in HwUnconfigured via the discovery stream",
"[lifecycle][REQ-LIFECYCLE-023]") {
"[lifecycle][REQ-LIFECYCLE-018][REQ-LIFECYCLE-026][REQ-LIFECYCLE-035]") {
WriterCtx via_discovery;
via_discovery.via_discovery_stream = true;
REQUIRE(field_writable(ServerState::HwUnconfigured, FieldKind::HwGeneric, via_discovery));
Expand All @@ -617,7 +635,7 @@ TEST_CASE("field_writable: HwGeneric is writable only in HwUnconfigured via the
TEST_CASE("field_writable: FunctionalW is unwritable in HwUnconfigured, needs authorization "
"(incl. discovery) while HwConfigured, and needs authorization (excl. discovery) once "
"RcpConfigured",
"[lifecycle][REQ-LIFECYCLE-023]") {
"[lifecycle][REQ-LIFECYCLE-019][REQ-LIFECYCLE-030][REQ-LIFECYCLE-036][REQ-LIFECYCLE-037]") {
WriterCtx via_discovery;
via_discovery.via_discovery_stream = true;
WriterCtx via_owning;
Expand All @@ -637,7 +655,7 @@ TEST_CASE("field_writable: FunctionalW is unwritable in HwUnconfigured, needs au

TEST_CASE("field_writable: FunctionalWStar is unconditionally writable in HwUnconfigured and "
"permanently locked once RcpConfigured",
"[lifecycle][REQ-LIFECYCLE-023]") {
"[lifecycle][REQ-LIFECYCLE-020]") {
WriterCtx none;
REQUIRE(field_writable(ServerState::HwUnconfigured, FieldKind::FunctionalWStar, none));

Expand All @@ -651,7 +669,7 @@ TEST_CASE("field_writable: FunctionalWStar is unconditionally writable in HwUnco
}

TEST_CASE("field_writable: ReadOnly is never writable, in any state, by any writer",
"[lifecycle][REQ-LIFECYCLE-023]") {
"[lifecycle]") {
WriterCtx via_owning;
via_owning.via_owning_stream = true;
REQUIRE_FALSE(field_writable(ServerState::HwUnconfigured, FieldKind::ReadOnly, via_owning));
Expand All @@ -660,7 +678,7 @@ TEST_CASE("field_writable: ReadOnly is never writable, in any state, by any writ
}

TEST_CASE("field_writable: a non-unicast frame denies an otherwise-writable field",
"[lifecycle][REQ-LIFECYCLE-023][REQ-LIFECYCLE-027]") {
"[lifecycle][REQ-LIFECYCLE-019][REQ-LIFECYCLE-027]") {
WriterCtx writer;
writer.via_owning_stream = true;
writer.via_non_unicast_frame = true;
Expand Down Expand Up @@ -735,3 +753,37 @@ TEST_CASE("field_write_error_w_plus distinguishes locked/state-locked from write
via_owning.via_owning_stream = true;
REQUIRE_FALSE(field_write_error_w_plus(ServerState::HwConfigured, via_owning, false).has_value());
}

// ── Error category ────────────────────────────────────────────────────────────

TEST_CASE("LifecycleErrc is a distinct error category with non-empty, distinct messages",
"[lifecycle][REQ-LIFECYCLE-021]") {
std::error_code invalid = make_error_code(LifecycleErrc::invalid_transition);
std::error_code hw_cfg = make_error_code(LifecycleErrc::hw_cfg_inconsistent);
std::error_code rcp_cfg = make_error_code(LifecycleErrc::rcp_cfg_inconsistent);
std::error_code unauth = make_error_code(LifecycleErrc::unauthorized);
std::error_code not_idle = make_error_code(LifecycleErrc::eps_not_idle);

REQUIRE(invalid.category() == lifecycle_category());
REQUIRE(hw_cfg.category() == lifecycle_category());
REQUIRE(rcp_cfg.category() == lifecycle_category());
REQUIRE(unauth.category() == lifecycle_category());
REQUIRE(not_idle.category() == lifecycle_category());

REQUIRE_FALSE(invalid.message().empty());
REQUIRE_FALSE(hw_cfg.message().empty());
REQUIRE_FALSE(rcp_cfg.message().empty());
REQUIRE_FALSE(unauth.message().empty());
REQUIRE_FALSE(not_idle.message().empty());

// Every message is distinct from every other.
const std::vector<std::string> messages{invalid.message(), hw_cfg.message(), rcp_cfg.message(),
unauth.message(), not_idle.message()};
for (size_t i = 0; i < messages.size(); ++i)
for (size_t j = i + 1; j < messages.size(); ++j) REQUIRE(messages[i] != messages[j]);

// An unrecognized ordinal still returns a non-empty message (the
// category's own default: branch), not an empty/garbage string.
std::error_code unknown(99, lifecycle_category());
REQUIRE_FALSE(unknown.message().empty());
}
Loading