Skip to content

Commit a0f446b

Browse files
tehampsonrestyled-commitsbzbarsky-apple
authored
Use FaultInjection to help with IDM_1_3 automation testing (project-chip#32045)
* Add FaultInjection to help with IDM_1_3 automation testing * Restyled by whitespace * Restyled by clang-format * Small fixes * Small change * Fix CI * Quick fix CI * Address PR comments * Quick Fix * Restyled by clang-format * Fix CI * Fix CI * use maybe_unused instead of (void) * Apply suggestions from code review Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Address PR comments * Restyled by whitespace * Restyled by clang-format * Address PR comment --------- Co-authored-by: Restyled.io <commits@restyled.io> Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
1 parent 4da852f commit a0f446b

9 files changed

+216
-5
lines changed

scripts/build/build/targets.py

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def BuildHostTarget():
152152
target.AppendModifier('nodeps', enable_ble=False, enable_wifi=False, enable_thread=False,
153153
crypto_library=HostCryptoLibrary.MBEDTLS, use_clang=True).ExceptIfRe('-(clang|noble|boringssl|mbedtls)')
154154

155+
target.AppendModifier('nlfaultinject', use_nl_fault_injection=True)
155156
target.AppendModifier('platform-mdns', use_platform_mdns=True)
156157
target.AppendModifier('minmdns-verbose', minmdns_high_verbosity=True)
157158
target.AppendModifier('libnl', minmdns_address_policy="libnl")

scripts/build/builders/host.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE,
295295
enable_ipv4=True, enable_ble=True, enable_wifi=True,
296296
enable_thread=True, use_tsan=False, use_asan=False, use_ubsan=False,
297297
separate_event_loop=True, fuzzing_type: HostFuzzingType = HostFuzzingType.NONE, use_clang=False,
298-
interactive_mode=True, extra_tests=False, use_platform_mdns=False, enable_rpcs=False,
298+
interactive_mode=True, extra_tests=False, use_nl_fault_injection=False, use_platform_mdns=False, enable_rpcs=False,
299299
use_coverage=False, use_dmalloc=False, minmdns_address_policy=None,
300300
minmdns_high_verbosity=False, imgui_ui=False, crypto_library: HostCryptoLibrary = None,
301301
enable_test_event_triggers=None):
@@ -368,6 +368,9 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE,
368368
# so setting clang is not correct
369369
raise Exception('Fake host board is always gcc (not clang)')
370370

371+
if use_nl_fault_injection:
372+
self.extra_gn_options.append('chip_with_nlfaultinjection=true')
373+
371374
if minmdns_address_policy:
372375
if use_platform_mdns:
373376
raise Exception('Address policy applies to minmdns only')

scripts/build/testdata/all_targets_linux_x64.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ efr32-{brd4161a,brd4187c,brd4186c,brd4163a,brd4164a,brd4166a,brd4170a,brd4186a,b
1010
esp32-{m5stack,c3devkit,devkitc,qemu}-{all-clusters,all-clusters-minimal,energy-management,ota-provider,ota-requestor,shell,light,lock,bridge,temperature-measurement,ota-requestor,tests}[-rpc][-ipv6only][-tracing]
1111
genio-lighting-app
1212
linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang]
13-
linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,microwave-oven,refrigerator,rvc,air-purifier,lit-icd,air-quality-sensor,network-manager,energy-management}[-nodeps][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui][-evse-test-event]
13+
linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,microwave-oven,refrigerator,rvc,air-purifier,lit-icd,air-quality-sensor,network-manager,energy-management}[-nodeps][-nlfaultinject][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui][-evse-test-event]
1414
linux-x64-efr32-test-runner[-clang]
1515
imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release]
1616
infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage]

src/app/CommandHandler.cpp

+130
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ Status CommandHandler::ProcessInvokeRequest(System::PacketBufferHandle && payloa
214214
SetGroupRequest(true);
215215
}
216216

217+
// When updating this code, please remember to make corresponding changes to TestOnlyInvokeCommandRequestWithFaultsInjected.
217218
VerifyOrReturnError(invokeRequestMessage.GetSuppressResponse(&mSuppressResponse) == CHIP_NO_ERROR, Status::InvalidAction);
218219
VerifyOrReturnError(invokeRequestMessage.GetTimedRequest(&mTimedRequest) == CHIP_NO_ERROR, Status::InvalidAction);
219220
VerifyOrReturnError(invokeRequestMessage.GetInvokeRequests(&invokeRequests) == CHIP_NO_ERROR, Status::InvalidAction);
@@ -911,6 +912,135 @@ void CommandHandler::MoveToState(const State aTargetState)
911912
ChipLogDetail(DataManagement, "Command handler moving to [%10.10s]", GetStateStr());
912913
}
913914

915+
#if CHIP_WITH_NLFAULTINJECTION
916+
917+
namespace {
918+
919+
CHIP_ERROR TestOnlyExtractCommandPathFromNextInvokeRequest(TLV::TLVReader & invokeRequestsReader,
920+
ConcreteCommandPath & concretePath)
921+
{
922+
ReturnErrorOnFailure(invokeRequestsReader.Next(TLV::AnonymousTag()));
923+
CommandDataIB::Parser commandData;
924+
ReturnErrorOnFailure(commandData.Init(invokeRequestsReader));
925+
CommandPathIB::Parser commandPath;
926+
ReturnErrorOnFailure(commandData.GetPath(&commandPath));
927+
return commandPath.GetConcreteCommandPath(concretePath);
928+
}
929+
930+
[[maybe_unused]] const char * GetFaultInjectionTypeStr(CommandHandler::NlFaultInjectionType faultType)
931+
{
932+
switch (faultType)
933+
{
934+
case CommandHandler::NlFaultInjectionType::SeparateResponseMessages:
935+
return "Each response will be sent in a separate InvokeResponseMessage. The order of responses will be the same as the "
936+
"original request.";
937+
case CommandHandler::NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder:
938+
return "Each response will be sent in a separate InvokeResponseMessage. The order of responses will be reversed from the "
939+
"original request.";
940+
case CommandHandler::NlFaultInjectionType::SkipSecondResponse:
941+
return "Single InvokeResponseMessages. Dropping response to second request";
942+
}
943+
VerifyOrDieWithMsg(false, DataManagement, "TH Failure: Unexpected fault type");
944+
}
945+
946+
} // anonymous namespace
947+
948+
// This method intentionally duplicates code from other sections. While code consolidation
949+
// is generally preferred, here we prioritize generating a clear crash message to aid in
950+
// troubleshooting test failures.
951+
void CommandHandler::TestOnlyInvokeCommandRequestWithFaultsInjected(Messaging::ExchangeContext * ec,
952+
System::PacketBufferHandle && payload, bool isTimedInvoke,
953+
NlFaultInjectionType faultType)
954+
{
955+
VerifyOrDieWithMsg(ec != nullptr, DataManagement, "TH Failure: Incoming exchange context should not be null");
956+
VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "TH Failure: state should be Idle, issue with TH");
957+
958+
ChipLogProgress(DataManagement, "Response to InvokeRequestMessage overridden by fault injection");
959+
ChipLogProgress(DataManagement, " Injecting the following response:%s", GetFaultInjectionTypeStr(faultType));
960+
961+
mResponseSender.SetExchangeContext(ec);
962+
Handle workHandle(this);
963+
mResponseSender.WillSendMessage();
964+
VerifyOrDieWithMsg(!mResponseSender.IsForGroup(), DataManagement, "DUT Failure: Unexpected Group Command");
965+
966+
System::PacketBufferTLVReader reader;
967+
InvokeRequestMessage::Parser invokeRequestMessage;
968+
InvokeRequests::Parser invokeRequests;
969+
reader.Init(std::move(payload));
970+
VerifyOrDieWithMsg(invokeRequestMessage.Init(reader) == CHIP_NO_ERROR, DataManagement,
971+
"TH Failure: Failed 'invokeRequestMessage.Init(reader)'");
972+
#if CHIP_CONFIG_IM_PRETTY_PRINT
973+
invokeRequestMessage.PrettyPrint();
974+
#endif
975+
976+
VerifyOrDieWithMsg(invokeRequestMessage.GetSuppressResponse(&mSuppressResponse) == CHIP_NO_ERROR, DataManagement,
977+
"DUT Failure: Mandatory SuppressResponse field missing");
978+
VerifyOrDieWithMsg(invokeRequestMessage.GetTimedRequest(&mTimedRequest) == CHIP_NO_ERROR, DataManagement,
979+
"DUT Failure: Mandatory TimedRequest field missing");
980+
VerifyOrDieWithMsg(invokeRequestMessage.GetInvokeRequests(&invokeRequests) == CHIP_NO_ERROR, DataManagement,
981+
"DUT Failure: Mandatory InvokeRequests field missing");
982+
VerifyOrDieWithMsg(mTimedRequest == isTimedInvoke, DataManagement,
983+
"DUT Failure: TimedRequest value in message mismatches action");
984+
985+
{
986+
InvokeRequestMessage::Parser validationInvokeRequestMessage = invokeRequestMessage;
987+
VerifyOrDieWithMsg(ValidateInvokeRequestMessageAndBuildRegistry(validationInvokeRequestMessage) == CHIP_NO_ERROR,
988+
DataManagement, "DUT Failure: InvokeRequestMessage contents were invalid");
989+
}
990+
991+
TLV::TLVReader invokeRequestsReader;
992+
invokeRequests.GetReader(&invokeRequestsReader);
993+
994+
size_t commandCount = 0;
995+
VerifyOrDieWithMsg(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */) == CHIP_NO_ERROR,
996+
DataManagement,
997+
"TH Failure: Failed to get the length of InvokeRequests after InvokeRequestMessage validation");
998+
999+
// The command count check (specifically for a count of 2) is tied to IDM_1_3. This may need adjustment for
1000+
// compatibility with future test plans.
1001+
VerifyOrDieWithMsg(commandCount == 2, DataManagement, "DUT failure: We were strictly expecting exactly 2 InvokeRequests");
1002+
mReserveSpaceForMoreChunkMessages = true;
1003+
1004+
{
1005+
// Response path is the same as request path since we are replying with a failure message.
1006+
ConcreteCommandPath concreteResponsePath1;
1007+
ConcreteCommandPath concreteResponsePath2;
1008+
VerifyOrDieWithMsg(
1009+
TestOnlyExtractCommandPathFromNextInvokeRequest(invokeRequestsReader, concreteResponsePath1) == CHIP_NO_ERROR,
1010+
DataManagement, "DUT Failure: Issues encountered while extracting the ConcreteCommandPath from the first request");
1011+
VerifyOrDieWithMsg(
1012+
TestOnlyExtractCommandPathFromNextInvokeRequest(invokeRequestsReader, concreteResponsePath2) == CHIP_NO_ERROR,
1013+
DataManagement, "DUT Failure: Issues encountered while extracting the ConcreteCommandPath from the second request");
1014+
1015+
if (faultType == NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder)
1016+
{
1017+
ConcreteCommandPath temp(concreteResponsePath1);
1018+
concreteResponsePath1 = concreteResponsePath2;
1019+
concreteResponsePath2 = temp;
1020+
}
1021+
1022+
VerifyOrDieWithMsg(FallibleAddStatus(concreteResponsePath1, Status::Failure) == CHIP_NO_ERROR, DataManagement,
1023+
"TH Failure: Error adding the first InvokeResponse");
1024+
if (faultType == NlFaultInjectionType::SeparateResponseMessages ||
1025+
faultType == NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder)
1026+
{
1027+
VerifyOrDieWithMsg(FinalizeInvokeResponseMessageAndPrepareNext() == CHIP_NO_ERROR, DataManagement,
1028+
"TH Failure: Failed to create second InvokeResponseMessage");
1029+
}
1030+
if (faultType != NlFaultInjectionType::SkipSecondResponse)
1031+
{
1032+
VerifyOrDieWithMsg(FallibleAddStatus(concreteResponsePath2, Status::Failure) == CHIP_NO_ERROR, DataManagement,
1033+
"TH Failure: Error adding the second InvokeResponse");
1034+
}
1035+
}
1036+
1037+
VerifyOrDieWithMsg(invokeRequestsReader.Next() == CHIP_END_OF_TLV, DataManagement,
1038+
"DUT Failure: Unexpected TLV ending of InvokeRequests");
1039+
VerifyOrDieWithMsg(invokeRequestMessage.ExitContainer() == CHIP_NO_ERROR, DataManagement,
1040+
"DUT Failure: InvokeRequestMessage TLV is not properly terminated");
1041+
}
1042+
#endif // CHIP_WITH_NLFAULTINJECTION
1043+
9141044
} // namespace app
9151045
} // namespace chip
9161046

src/app/CommandHandler.h

+29
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,35 @@ class CommandHandler
429429
return mResponseSender.GetSubjectDescriptor();
430430
}
431431

432+
#if CHIP_WITH_NLFAULTINJECTION
433+
434+
enum class NlFaultInjectionType : uint8_t
435+
{
436+
SeparateResponseMessages,
437+
SeparateResponseMessagesAndInvertedResponseOrder,
438+
SkipSecondResponse
439+
};
440+
441+
/**
442+
* @brief Sends InvokeResponseMessages with injected faults for certification testing.
443+
*
444+
* The Test Harness (TH) uses this to simulate various server response behaviors,
445+
* ensuring the Device Under Test (DUT) handles responses per specification.
446+
*
447+
* This function strictly validates the DUT's InvokeRequestMessage against the test plan.
448+
* If deviations occur, the TH terminates with a detailed error message.
449+
*
450+
* @param ec Exchange context for sending InvokeResponseMessages to the client.
451+
* @param payload Payload of the incoming InvokeRequestMessage from the client.
452+
* @param isTimedInvoke Indicates whether the interaction is timed.
453+
* @param faultType The specific type of fault to inject into the response.
454+
*/
455+
// TODO(#30453): After refactoring CommandHandler for better unit testability, create a
456+
// unit test specifically for the fault injection behavior.
457+
void TestOnlyInvokeCommandRequestWithFaultsInjected(Messaging::ExchangeContext * ec, System::PacketBufferHandle && payload,
458+
bool isTimedInvoke, NlFaultInjectionType faultType);
459+
#endif // CHIP_WITH_NLFAULTINJECTION
460+
432461
private:
433462
friend class TestCommandInteraction;
434463
friend class CommandHandler::Handle;

src/app/ConcreteCommandPath.h

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ struct ConcreteCommandPath : public ConcreteClusterPath
3333
ConcreteClusterPath(aEndpointId, aClusterId), mCommandId(aCommandId)
3434
{}
3535

36+
ConcreteCommandPath() : ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId), mCommandId(kInvalidCommandId) {}
37+
3638
bool operator==(const ConcreteCommandPath & aOther) const
3739
{
3840
return ConcreteClusterPath::operator==(aOther) && (mCommandId == aOther.mCommandId);

src/app/InteractionModelEngine.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <app/util/endpoint-config-api.h>
3636
#include <lib/core/Global.h>
3737
#include <lib/core/TLVUtilities.h>
38+
#include <lib/support/CHIPFaultInjection.h>
3839
#include <lib/support/CodeUtils.h>
3940
#include <lib/support/FibonacciUtils.h>
4041

@@ -411,6 +412,21 @@ Status InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext
411412
ChipLogProgress(InteractionModel, "no resource for Invoke interaction");
412413
return Status::Busy;
413414
}
415+
CHIP_FAULT_INJECT(
416+
FaultInjection::kFault_IMInvoke_SeparateResponses,
417+
commandHandler->TestOnlyInvokeCommandRequestWithFaultsInjected(
418+
apExchangeContext, std::move(aPayload), aIsTimedInvoke, CommandHandler::NlFaultInjectionType::SeparateResponseMessages);
419+
return Status::Success;);
420+
CHIP_FAULT_INJECT(FaultInjection::kFault_IMInvoke_SeparateResponsesInvertResponseOrder,
421+
commandHandler->TestOnlyInvokeCommandRequestWithFaultsInjected(
422+
apExchangeContext, std::move(aPayload), aIsTimedInvoke,
423+
CommandHandler::NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder);
424+
return Status::Success;);
425+
CHIP_FAULT_INJECT(
426+
FaultInjection::kFault_IMInvoke_SkipSecondResponse,
427+
commandHandler->TestOnlyInvokeCommandRequestWithFaultsInjected(apExchangeContext, std::move(aPayload), aIsTimedInvoke,
428+
CommandHandler::NlFaultInjectionType::SkipSecondResponse);
429+
return Status::Success;);
414430
commandHandler->OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), aIsTimedInvoke);
415431
return Status::Success;
416432
}

src/lib/support/CHIPFaultInjection.cpp

+15-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,21 @@ static int32_t sFault_FuzzExchangeHeader_Arguments[1];
3535
static class nl::FaultInjection::Manager sChipFaultInMgr;
3636
static const nl::FaultInjection::Name sManagerName = "chip";
3737
static const nl::FaultInjection::Name sFaultNames[] = {
38-
"AllocExchangeContext", "DropIncomingUDPMsg", "DropOutgoingUDPMsg", "AllocBinding", "SendAlarm",
39-
"HandleAlarm", "FuzzExchangeHeaderTx", "RMPDoubleTx", "RMPSendError", "BDXBadBlockCounter",
40-
"BDXAllocTransfer", "CASEKeyConfirm", "SecMgrBusy",
38+
"AllocExchangeContext",
39+
"DropIncomingUDPMsg",
40+
"DropOutgoingUDPMsg",
41+
"AllocBinding",
42+
"SendAlarm",
43+
"HandleAlarm",
44+
"FuzzExchangeHeaderTx",
45+
"RMPDoubleTx",
46+
"RMPSendError",
47+
"BDXBadBlockCounter",
48+
"BDXAllocTransfer",
49+
"SecMgrBusy",
50+
"IMInvoke_SeparateResponses",
51+
"IMInvoke_SeparateResponsesInvertResponseOrder",
52+
"IMInvoke_SkipSecondResponse",
4153
#if CONFIG_NETWORK_LAYER_BLE
4254
"CHIPOBLESend",
4355
#endif // CONFIG_NETWORK_LAYER_BLE

src/lib/support/CHIPFaultInjection.h

+18
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
#include <lib/core/CHIPConfig.h>
2727

2828
#if CHIP_WITH_NLFAULTINJECTION
29+
#ifdef NDEBUG
30+
// TODO(#30453): After fixing the issue where CHIP_WITH_NLFAULTINJECTION is seemingly enabled on release builds,
31+
// uncomment the line below.
32+
// static_assert(false, "CHIP_WITH_NLFAULTINJECTION should NOT be enabled on release build");
33+
#endif
2934

3035
#include <nlfaultinjection.hpp>
3136

@@ -57,12 +62,25 @@ typedef enum
5762
kFault_BDXBadBlockCounter, /**< Corrupt the BDX Block Counter in the BDX BlockSend or BlockEOF message about to be sent */
5863
kFault_BDXAllocTransfer, /**< Fail the allocation of a BDXTransfer object */
5964
kFault_SecMgrBusy, /**< Trigger a WEAVE_ERROR_SECURITY_MANAGER_BUSY when starting an authentication session */
65+
kFault_IMInvoke_SeparateResponses, /**< Validate incoming InvokeRequestMessage contains exactly 2 valid commands and respond
66+
with 2 InvokeResponseMessages */
67+
kFault_IMInvoke_SeparateResponsesInvertResponseOrder, /**< Validate incoming InvokeRequestMessage contains exactly 2 valid
68+
commands and respond with 2 InvokeResponseMessages where the response order is inverted
69+
compared to the request order */
70+
kFault_IMInvoke_SkipSecondResponse, /**< Validate incoming InvokeRequestMessage contains exactly 2 valid commands and respond
71+
with 1 InvokeResponseMessage, dropping the response to the second request */
6072
#if CONFIG_NETWORK_LAYER_BLE
6173
kFault_CHIPOBLESend, /**< Inject a GATT error when sending the first fragment of a chip message over BLE */
6274
#endif // CONFIG_NETWORK_LAYER_BLE
6375
kFault_NumItems,
6476
} Id;
6577

78+
static_assert(kFault_IMInvoke_SeparateResponses == 12, "Test plan specification and automation code relies on this value being 12");
79+
static_assert(kFault_IMInvoke_SeparateResponsesInvertResponseOrder == 13,
80+
"Test plan specification and automation code relies on this value being 13");
81+
static_assert(kFault_IMInvoke_SkipSecondResponse == 14,
82+
"Test plan specification and automation code relies on this value being 14");
83+
6684
DLL_EXPORT nl::FaultInjection::Manager & GetManager();
6785

6886
/**

0 commit comments

Comments
 (0)