diff --git a/Svc/CmdSequencer/CmdSequencerImpl.cpp b/Svc/CmdSequencer/CmdSequencerImpl.cpp index 45f0224241..6442ed4abe 100644 --- a/Svc/CmdSequencer/CmdSequencerImpl.cpp +++ b/Svc/CmdSequencer/CmdSequencerImpl.cpp @@ -176,9 +176,7 @@ void CmdSequencerComponentImpl::doSequenceRun(const Fw::StringBase& filename) { this->log_ACTIVITY_HI_CS_PortSequenceStarted(this->m_sequence->getLogFileName()); } -void CmdSequencerComponentImpl::seqRunIn_handler(FwIndexType portNum, - const Fw::StringBase& filename, - const Svc::SeqArgs& args) { +void CmdSequencerComponentImpl::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename, const Svc::SeqArgs& args) { (void)args; // Suppress unused parameter warning this->doSequenceRun(filename); } diff --git a/Svc/FpySequencer/FpySequencer.hpp b/Svc/FpySequencer/FpySequencer.hpp index 6d41d4b91f..ca391dde28 100644 --- a/Svc/FpySequencer/FpySequencer.hpp +++ b/Svc/FpySequencer/FpySequencer.hpp @@ -145,6 +145,14 @@ class FpySequencer : public FpySequencerComponentBase { const Fw::CmdStringArg& fileName, //!< The name of the sequence file FpySequencer_BlockState block //!< Return command status when complete or not ) override; + + //! Handler implementation for command RUN_ARGS + void RUN_ARGS_cmdHandler(FwOpcodeType opCode, //!< The opcode + U32 cmdSeq, //!< The command sequence number + const Fw::CmdStringArg& fileName, //!< The name of the sequence file + Svc::FpySequencer_BlockState block, //!< Return command status when complete or not + Svc::SeqArgs args //!< Arguments to pass to the sequencer + ) override; //! Handler implementation for command RUN_ARGS void RUN_ARGS_cmdHandler(FwOpcodeType opCode, //!< The opcode @@ -161,7 +169,6 @@ class FpySequencer : public FpySequencerComponentBase { U32 cmdSeq, //!< The command sequence number const Fw::CmdStringArg& fileName //!< The name of the sequence file ) override; - //! Handler implementation for command VALIDATE_ARGS //! //! Loads and validates a sequence with arguments @@ -269,6 +276,15 @@ class FpySequencer : public FpySequencerComponentBase { Svc_FpySequencer_SequencerStateMachine::Signal signal, //!< The signal const Svc::FpySequencer_SequenceExecutionArgs& value //!< The value ) override; + + //! Implementation for action setSequenceArguments of state machine Svc_FpySequencer_SequencerStateMachine + //! + //! sets the arguments to pass to the sequence + void Svc_FpySequencer_SequencerStateMachine_action_setSequenceArguments( + SmId smId, //!< The state machine id + Svc_FpySequencer_SequencerStateMachine::Signal signal, //!< The signal + const Svc::FpySequencer_SequenceExecutionArgs& value //!< The value + ) override; //! Implementation for action setSequenceArguments of state machine Svc_FpySequencer_SequencerStateMachine //! @@ -359,6 +375,14 @@ class FpySequencer : public FpySequencerComponentBase { SmId smId, //!< The state machine id Svc_FpySequencer_SequencerStateMachine::Signal signal //!< The signal ) override; + + //! Implementation for action clearSequenceArguments of state machine Svc_FpySequencer_SequencerStateMachine + //! + //! clears arguments + void Svc_FpySequencer_SequencerStateMachine_action_clearSequenceArguments( + SmId smId, //!< The state machine id + Svc_FpySequencer_SequencerStateMachine::Signal signal //!< The signal + ) override; //! Implementation for action clearSequenceArguments of state machine Svc_FpySequencer_SequencerStateMachine //! @@ -640,6 +664,10 @@ class FpySequencer : public FpySequencerComponentBase { // live running computation of CRC (updated as we read) U32 m_computedCRC; + // Size of arguments read in current sequence. Used for validation between + // User provided arguments and what is requested of the sequence. + Fpy::StackSizeType m_totalExpectedArgSize{0}; + // whether or not the sequence we're about to run should return immediately or // block on completion FpySequencer_BlockState m_sequenceBlockState; diff --git a/Svc/FpySequencer/FpySequencerEvents.fppi b/Svc/FpySequencer/FpySequencerEvents.fppi index 06c75d6a28..cf1ca452f7 100644 --- a/Svc/FpySequencer/FpySequencerEvents.fppi +++ b/Svc/FpySequencer/FpySequencerEvents.fppi @@ -219,6 +219,23 @@ event TooManySequenceDirectives( severity warning high \ format "A sequence specified it had {} directives but the max was {}" +event ArgSizeMismatch( + expected: Fpy.StackSizeType + actual: FwSizeType + filePath: string +) \ + severity warning high \ + format "Expected {} bytes of arguments, but received {} in sequence file {}" + +event ArgTotalSizeExceedsStackLimit( + argSize: Fpy.StackSizeType + maxStackSize: Fpy.StackSizeType + filePath: string +) \ + severity warning high \ + format "Adding argument of size {} would exceed max stack size {} in sequence file {}" + + event SequencePaused( stmtIdx: U32 ) \ diff --git a/Svc/FpySequencer/FpySequencerTypes.fpp b/Svc/FpySequencer/FpySequencerTypes.fpp index 69d0fb42db..ade2b06c1f 100644 --- a/Svc/FpySequencer/FpySequencerTypes.fpp +++ b/Svc/FpySequencer/FpySequencerTypes.fpp @@ -1,7 +1,7 @@ module Svc { module Fpy { @ the current schema version (must be representable in U8) - constant SCHEMA_VERSION = 5; + constant SCHEMA_VERSION = 6; @ the type which everything referencing a size or offset on the stack is represented in # we use a U32 because U16 is too small (would only allow up to 65 kB max stack size) @@ -130,6 +130,20 @@ module Svc { CMD_FAIL = 17 } + @ Maximum length for argument or type names in arg_specs + @ Fpy serializes these as U8 length prefix, so max is 255 + constant MAX_ARG_SPEC_NAME_LEN = 255 + + @ Argument specification describing an input argument's name, type, and stack size + @ NOTE: This struct does NOT use FPP's auto-generated serialization! + @ Serialization is handled manually in C++ to match Fpy's format: + struct ArgSpec { + argName: string size MAX_ARG_SPEC_NAME_LEN + typeName: string size MAX_ARG_SPEC_NAME_LEN + @ Size of this argument on the stack in bytes + argSize: StackSizeType + } default {argName = "", typeName = "", argSize = 0 } + struct Header { @ the major version of the FSW majorVersion: U8 @@ -165,7 +179,7 @@ module Svc { header: Header @ an array of size m_header.argumentCount mapping argument position to local @ variable index - args: [MAX_SEQUENCE_ARG_COUNT] U8 + args: [MAX_SEQUENCE_ARG_COUNT] ArgSpec statements: [MAX_SEQUENCE_STATEMENT_COUNT] Statement footer: Footer } diff --git a/Svc/FpySequencer/FpySequencerValidationState.cpp b/Svc/FpySequencer/FpySequencerValidationState.cpp index 4b40f463c4..3074aba544 100644 --- a/Svc/FpySequencer/FpySequencerValidationState.cpp +++ b/Svc/FpySequencer/FpySequencerValidationState.cpp @@ -73,7 +73,7 @@ Fw::Success FpySequencer::validate() { if (readStatus != Fw::Success::SUCCESS) { return Fw::Success::FAILURE; } - + // read footer bytes but don't include in CRC readStatus = this->readBytes(sequenceFile, Fpy::Footer::SERIALIZED_SIZE, FpySequencer_FileReadStage::FOOTER, false); @@ -145,20 +145,71 @@ Fw::Success FpySequencer::readHeader() { // return SUCCESS if sequence is valid, FAILURE otherwise Fw::Success FpySequencer::readBody() { Fw::SerializeStatus deserStatus; - // deser body: - // deser arg mappings - for (U8 argMappingIdx = 0; argMappingIdx < this->m_sequenceObj.get_header().get_argumentCount(); argMappingIdx++) { - // serializable register index of arg $argMappingIdx - // TODO should probably check that this serReg is inside range - deserStatus = this->m_sequenceBuffer.deserializeTo(this->m_sequenceObj.get_args()[argMappingIdx]); - if (deserStatus != Fw::FW_SERIALIZE_OK) { + + const U8 argumentCount = this->m_sequenceObj.get_header().get_argumentCount(); + this->m_totalExpectedArgSize = 0; + + // deser arguments + // Read and deserialize each arg_spec incrementally since they're variable-length + for (U8 i = 0; i < argumentCount; i++) { + Fpy::ArgSpec& argSpec = this->m_sequenceObj.get_args()[i]; + Fw::String myString{}; + // Read arg_name (length + string) + deserStatus = this->m_sequenceBuffer.deserializeTo(myString); + if (deserStatus != Fw::SerializeStatus::FW_SERIALIZE_OK) { + this->log_WARNING_HI_FileReadDeserializeError( + FpySequencer_FileReadStage::BODY, + this->m_sequenceFilePath, + static_cast(deserStatus), + this->m_sequenceBuffer.getDeserializeSizeLeft(), + this->m_sequenceBuffer.getSize() + ); + return Fw::Success::FAILURE; + } + argSpec.set_argName(myString); + + // Read type_name (length + string) + deserStatus = this->m_sequenceBuffer.deserializeTo(myString); + if (deserStatus != Fw::SerializeStatus::FW_SERIALIZE_OK) { + this->log_WARNING_HI_FileReadDeserializeError( + FpySequencer_FileReadStage::BODY, + this->m_sequenceFilePath, + static_cast(deserStatus), + this->m_sequenceBuffer.getDeserializeSizeLeft(), + this->m_sequenceBuffer.getSize() + ); + return Fw::Success::FAILURE; + } + argSpec.set_typeName(myString); + + // Read and deserialize size field + Fpy::StackSizeType argSize = 0; + deserStatus = this->m_sequenceBuffer.deserializeTo(argSize); + if (deserStatus != Fw::SerializeStatus::FW_SERIALIZE_OK) { this->log_WARNING_HI_FileReadDeserializeError( FpySequencer_FileReadStage::BODY, this->m_sequenceFilePath, static_cast(deserStatus), this->m_sequenceBuffer.getDeserializeSizeLeft(), this->m_sequenceBuffer.getSize()); return Fw::Success::FAILURE; } + + // Check for overflow before accumulation + if (m_totalExpectedArgSize > Fpy::MAX_STACK_SIZE - argSize) { + this->log_WARNING_HI_ArgTotalSizeExceedsStackLimit(argSize, Fpy::MAX_STACK_SIZE, + this->m_sequenceFilePath); + return Fw::Success::FAILURE; + } + argSpec.set_argSize(argSize); + m_totalExpectedArgSize += argSize; } + // Validate total argument size + if (this->m_totalExpectedArgSize != this->m_sequenceArgs.get_size()){ + this->log_WARNING_HI_ArgSizeMismatch(this->m_sequenceObj.get_header().get_argumentCount(), + this->m_sequenceArgs.get_size(), + this->m_sequenceFilePath); + return Fw::Success::FAILURE; + } + // deser statements for (U16 statementIdx = 0; statementIdx < this->m_sequenceObj.get_header().get_statementCount(); statementIdx++) { // deser statement diff --git a/Svc/FpySequencer/test/ut/FpySequencerTestMain.cpp b/Svc/FpySequencer/test/ut/FpySequencerTestMain.cpp index 90e03648fc..6f1abf6a03 100644 --- a/Svc/FpySequencer/test/ut/FpySequencerTestMain.cpp +++ b/Svc/FpySequencer/test/ut/FpySequencerTestMain.cpp @@ -2119,9 +2119,11 @@ TEST_F(FpySequencerTester, cmd_RUN) { TEST_F(FpySequencerTester, cmd_RUN_ARGS) { allocMem(); - add_LOAD_REL(0, 4); // Load first arg (U32 at offset 0) - duplicates it on stack - add_LOAD_REL(4, 4); // Load second arg (U32 at offset 4) - duplicates it on stack - add_DISCARD(16); // Discard all: 2 loaded copies + 2 original args + addArgumentSpec("arg1", "U32", sizeof(U32)); + addArgumentSpec("arg2", "U32", sizeof(U32)); + add_LOAD_REL(0, sizeof(U32)); // Load first arg (U32 at offset 0) - duplicates it on stack + add_LOAD_REL(4, sizeof(U32)); // Load second arg (U32 at offset 4) - duplicates it on stack + add_DISCARD(16); // Discard all: 2 loaded copies + 2 original args writeToFile("test.bin"); // Pass two U32 args: 10 and 20 @@ -2247,9 +2249,11 @@ TEST_F(FpySequencerTester, cmd_RUN_VALIDATED) { TEST_F(FpySequencerTester, cmd_VALIDATE_ARGS) { allocMem(); - add_LOAD_REL(0, 4); // Load first arg (U32 at offset 0) - duplicates it on stack - add_LOAD_REL(4, 4); // Load second arg (U32 at offset 4) - duplicates it on stack - add_DISCARD(16); // Discard all: 2 loaded copies + 2 original args + addArgumentSpec("arg1", "U32", sizeof(U32)); + addArgumentSpec("arg2", "U32", sizeof(U32)); + add_LOAD_REL(0, sizeof(U32)); // Load first arg (U32 at offset 0) - duplicates it on stack + add_LOAD_REL(4, sizeof(U32)); // Load second arg (U32 at offset 4) - duplicates it on stack + add_DISCARD(16); // Discard all: 2 loaded copies + 2 original args writeToFile("test.bin"); // Pass two U32 args: 10 and 20 @@ -2331,6 +2335,125 @@ TEST_F(FpySequencerTester, cmd_VALIDATE_ARGS_oversized) { removeFile("test.bin"); } +TEST_F(FpySequencerTester, cmd_VALIDATE_ARGS_zero_length_arg_name) { + allocMem(); + clearSeq(); + + // Create arg_spec with zero-length arg name (valid) + addArgumentSpec("", "U32", sizeof(U32)); // Empty string for arg name + add_NO_OP(); + writeToFile("test.bin"); + + // Create valid args buffer + Svc::SeqArgs args{0, 0}; + Fw::ExternalSerializeBuffer argBuf(args.get_buffer(), SequenceArgumentsMaxSize); + U32 arg1Val = 42; + ASSERT_EQ(argBuf.serializeFrom(arg1Val), Fw::FW_SERIALIZE_OK); + args.set_size(argBuf.getSize()); + + sendCmd_VALIDATE_ARGS(0, 0, Fw::String("test.bin"), args); + dispatchUntilState(State::VALIDATING); + ASSERT_EQ(tester_get_m_sequencesStarted(), 0); + ASSERT_EQ(tester_get_m_statementsDispatched(), 0); + dispatchUntilState(State::AWAITING_CMD_RUN_VALIDATED); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, Svc::FpySequencerTester::get_OPCODE_VALIDATE_ARGS(), 0, Fw::CmdResponse::OK); + + removeFile("test.bin"); +} + +TEST_F(FpySequencerTester, cmd_VALIDATE_ARGS_zero_length_type_name) { + allocMem(); + clearSeq(); + + // Create arg_spec with zero-length type name (valid) + addArgumentSpec("arg1", "", sizeof(U32)); // Empty string for type name + add_NO_OP(); + writeToFile("test.bin"); + + // Create valid args buffer + Svc::SeqArgs args{0, 0}; + Fw::ExternalSerializeBuffer argBuf(args.get_buffer(), SequenceArgumentsMaxSize); + U32 arg1Val = 42; + ASSERT_EQ(argBuf.serializeFrom(arg1Val), Fw::FW_SERIALIZE_OK); + args.set_size(argBuf.getSize()); + + sendCmd_VALIDATE_ARGS(0, 0, Fw::String("test.bin"), args); + dispatchUntilState(State::VALIDATING); + ASSERT_EQ(tester_get_m_sequencesStarted(), 0); + ASSERT_EQ(tester_get_m_statementsDispatched(), 0); + dispatchUntilState(State::AWAITING_CMD_RUN_VALIDATED); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, Svc::FpySequencerTester::get_OPCODE_VALIDATE_ARGS(), 0, Fw::CmdResponse::OK); + + removeFile("test.bin"); +} + +TEST_F(FpySequencerTester, cmd_VALIDATE_ARGS_max_length_strings) { + allocMem(); + clearSeq(); + + // Create arg_spec with maximum length strings (255 bytes each) + char maxLengthName[256]; + memset(maxLengthName, 'A', 255); + maxLengthName[255] = '\0'; + + char maxLengthType[256]; + memset(maxLengthType, 'B', 255); + maxLengthType[255] = '\0'; + + addArgumentSpec(maxLengthName, maxLengthType, sizeof(U32)); + add_LOAD_REL(0, sizeof(U32)); + add_DISCARD(sizeof(U32) * 2); // Discard loaded copy + original + writeToFile("test.bin"); + + // Create valid args buffer + Svc::SeqArgs args{0, 0}; + Fw::ExternalSerializeBuffer argBuf(args.get_buffer(), SequenceArgumentsMaxSize); + U32 arg1Val = 123; + ASSERT_EQ(argBuf.serializeFrom(arg1Val), Fw::FW_SERIALIZE_OK); + args.set_size(argBuf.getSize()); + + sendCmd_VALIDATE_ARGS(0, 0, Fw::String("test.bin"), args); + dispatchUntilState(State::VALIDATING); + dispatchUntilState(State::AWAITING_CMD_RUN_VALIDATED); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, Svc::FpySequencerTester::get_OPCODE_VALIDATE_ARGS(), 0, Fw::CmdResponse::OK); + + removeFile("test.bin"); +} + +TEST_F(FpySequencerTester, cmd_VALIDATE_ARGS_size_mismatch) { + allocMem(); + clearSeq(); + + addArgumentSpec("arg", "U64", sizeof(U64)); // U64 Arg + add_NO_OP(); + writeToFile("test.bin"); + + // Create args buffer with actual U32 (4 bytes) + Svc::SeqArgs args{0, 0}; + Fw::ExternalSerializeBuffer argBuf(args.get_buffer(), SequenceArgumentsMaxSize); + U32 arg1Val = 99; // Passing in a U32 + ASSERT_EQ(argBuf.serializeFrom(arg1Val), Fw::FW_SERIALIZE_OK); + args.set_size(argBuf.getSize()); + + sendCmd_VALIDATE_ARGS(0, 0, Fw::String("test.bin"), args); + dispatchUntilState(State::VALIDATING); + // Should fail due to size mismatch: expected 8 bytes (from arg_spec), got 4 bytes (from args) + dispatchUntilState(State::IDLE); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, Svc::FpySequencerTester::get_OPCODE_VALIDATE_ARGS(), 0, Fw::CmdResponse::EXECUTION_ERROR); + ASSERT_from_seqDoneOut_SIZE(1); + ASSERT_from_seqDoneOut(0, 0, 0, Fw::CmdResponse::EXECUTION_ERROR); + + // Verify ArgSizeMismatch event was logged + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_ArgSizeMismatch_SIZE(1); + + removeFile("test.bin"); +} + TEST_F(FpySequencerTester, cmd_CANCEL) { this->tester_setState(State::IDLE); sendCmd_CANCEL(0, 0); @@ -2594,54 +2717,76 @@ TEST_F(FpySequencerTester, readHeader) { } TEST_F(FpySequencerTester, readBody) { - U8 data[Fpy::MAX_SEQUENCE_ARG_COUNT + Fpy::MAX_SEQUENCE_STATEMENT_COUNT * Fpy::Statement::SERIALIZED_SIZE]; + FwSizeType argSpecSize = Fpy::MAX_SEQUENCE_ARG_COUNT * Fpy::ArgSpec::SERIALIZED_SIZE; + FwSizeType stmtSize = Fpy::MAX_SEQUENCE_STATEMENT_COUNT * Fpy::Statement::SERIALIZED_SIZE; - tester_get_m_sequenceBuffer_ptr()->setExtBuffer(data, sizeof(data)); - // write some args mappings + U8 data[argSpecSize + stmtSize]; + + tester_get_m_sequenceBuffer_ptr()->setExtBuffer(data, sizeof(data)); + + // write some argSpecs + tester_get_m_sequenceBuffer_ptr()->resetSer(); + Svc::SeqArgs maxArgs{0, 0}; + maxArgs.set_size(Fpy::MAX_SEQUENCE_ARG_COUNT * sizeof(U32)); + tester_set_m_sequenceArgs(maxArgs); for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_ARG_COUNT; ii++) { - // map arg idx ii to serReg pos 123 - ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(static_cast(123)), - Fw::SerializeStatus::FW_SERIALIZE_OK); + Fw::String argName; + argName.format("arg%u", ii); + Fw::String typeName("U32"); + ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(argName), Fw::SerializeStatus::FW_SERIALIZE_OK); + ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(typeName), Fw::SerializeStatus::FW_SERIALIZE_OK); + ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(static_cast(sizeof(U32))), Fw::SerializeStatus::FW_SERIALIZE_OK); + } + tester_get_m_sequenceObj_ptr()->get_header().set_argumentCount(Fpy::MAX_SEQUENCE_ARG_COUNT); + tester_get_m_sequenceObj_ptr()->get_header().set_statementCount(0); + + ASSERT_EQ(tester_readBody(), Fw::Success::SUCCESS); + + for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_ARG_COUNT; ii++) { + Fw::String expectedArgName; + expectedArgName.format("arg%u", ii); + ASSERT_EQ(tester_get_m_sequenceObj_ptr()->get_args()[ii].get_argName(), expectedArgName); + ASSERT_EQ(tester_get_m_sequenceObj_ptr()->get_args()[ii].get_typeName(), Fw::String("U32")); + ASSERT_EQ(tester_get_m_sequenceObj_ptr()->get_args()[ii].get_argSize(), sizeof(U32)); + } + + // check not writing enough arguments + // -1 intended mistake + tester_get_m_sequenceBuffer_ptr()->resetSer(); + + for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_ARG_COUNT - 1; ii++) { + ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(""), Fw::SerializeStatus::FW_SERIALIZE_OK); + ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(""), Fw::SerializeStatus::FW_SERIALIZE_OK); + ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(static_cast(sizeof(U32))), Fw::SerializeStatus::FW_SERIALIZE_OK); } + ASSERT_EQ(tester_readBody(), Fw::Success::FAILURE); + + tester_get_m_sequenceBuffer_ptr()->resetSer(); + // write some statements + Svc::SeqArgs noArgs{0, 0}; + tester_set_m_sequenceArgs(noArgs); + tester_get_m_sequenceObj_ptr()->get_header().set_argumentCount(0); Fpy::Statement stmt(Fpy::DirectiveId::NO_OP, Fw::StatementArgBuffer()); for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_STATEMENT_COUNT; ii++) { ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(stmt), Fw::SerializeStatus::FW_SERIALIZE_OK); } - tester_get_m_sequenceObj_ptr()->get_header().set_argumentCount(Fpy::MAX_SEQUENCE_ARG_COUNT); tester_get_m_sequenceObj_ptr()->get_header().set_statementCount(Fpy::MAX_SEQUENCE_STATEMENT_COUNT); ASSERT_EQ(tester_readBody(), Fw::Success::SUCCESS); - for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_ARG_COUNT; ii++) { - ASSERT_EQ(tester_get_m_sequenceObj_ptr()->get_args()[ii], 123); - } - for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_STATEMENT_COUNT; ii++) { ASSERT_EQ(tester_get_m_sequenceObj_ptr()->get_statements()[ii], stmt); } tester_get_m_sequenceBuffer_ptr()->resetSer(); - tester_get_m_sequenceObj_ptr()->get_header().set_statementCount(0); - // now see what happens if we don't write enough args - for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_ARG_COUNT - 1; ii++) { - // map arg idx ii to serReg pos 123 - ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(static_cast(123)), - Fw::SerializeStatus::FW_SERIALIZE_OK); - } - // don't write any stmts otherwise their bytes will be interpreted as arg mappings and it will trigger - // the wrong branch + tester_get_m_sequenceObj_ptr()->get_header().set_statementCount(1); + // don't write any statements - should fail ASSERT_EQ(tester_readBody(), Fw::Success::FAILURE); // now see what happens if we don't write enough stmts tester_get_m_sequenceBuffer_ptr()->resetSer(); - tester_get_m_sequenceObj_ptr()->get_header().set_argumentCount(Fpy::MAX_SEQUENCE_ARG_COUNT); tester_get_m_sequenceObj_ptr()->get_header().set_statementCount(Fpy::MAX_SEQUENCE_STATEMENT_COUNT); - for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_ARG_COUNT; ii++) { - // map arg idx ii to serReg pos 123 - ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(static_cast(123)), - Fw::SerializeStatus::FW_SERIALIZE_OK); - } // the -1 here is the intended mistake for (U32 ii = 0; ii < Fpy::MAX_SEQUENCE_STATEMENT_COUNT - 1; ii++) { ASSERT_EQ(tester_get_m_sequenceBuffer_ptr()->serializeFrom(stmt), Fw::SerializeStatus::FW_SERIALIZE_OK); @@ -3362,6 +3507,8 @@ TEST_F(FpySequencerTester, seqCancelIn) { TEST_F(FpySequencerTester, seqRunInArgs) { allocMem(); + addArgumentSpec("arg1", "U32", sizeof(U32)); + addArgumentSpec("arg2", "U32", sizeof(U32)); add_LOAD_REL(0, 4); // Load first arg (U32 at offset 0) - duplicates it on stack add_LOAD_REL(4, 4); // Load second arg (U32 at offset 4) - duplicates it on stack add_DISCARD(16); // Discard all: 2 loaded copies + 2 original args diff --git a/Svc/FpySequencer/test/ut/FpySequencerTester.cpp b/Svc/FpySequencer/test/ut/FpySequencerTester.cpp index 46ebeeb2d2..2b38f67f04 100644 --- a/Svc/FpySequencer/test/ut/FpySequencerTester.cpp +++ b/Svc/FpySequencer/test/ut/FpySequencerTester.cpp @@ -63,21 +63,31 @@ void FpySequencerTester::writeToFile(const char* name, FwSizeType maxBytes) { Fw::ExternalSerializeBuffer buf; buf.setExtBuffer(data, sizeof(data)); - // first let's calculate the size of the body. do this by just writing the body, - // then calculating how big that was, then clearing and writing the header, then writing the body again + // Calculate body size (arg_specs and statements) for (U32 ii = 0; ii < seq.get_header().get_argumentCount(); ii++) { ASSERT_EQ(buf.serializeFrom(seq.get_args()[ii]), Fw::SerializeStatus::FW_SERIALIZE_OK); } for (U32 ii = 0; ii < seq.get_header().get_statementCount(); ii++) { ASSERT_EQ(buf.serializeFrom(seq.get_statements()[ii]), Fw::SerializeStatus::FW_SERIALIZE_OK); - } + } + seq.get_header().set_bodySize(static_cast(buf.getSize())); buf.resetSer(); + // Write header using FPP auto-generated serialization ASSERT_EQ(buf.serializeFrom(seq.get_header()), Fw::SerializeStatus::FW_SERIALIZE_OK); + + // Write arg_specs in Fpy variable-length format for (U32 ii = 0; ii < seq.get_header().get_argumentCount(); ii++) { - ASSERT_EQ(buf.serializeFrom(seq.get_args()[ii]), Fw::SerializeStatus::FW_SERIALIZE_OK); + const Fpy::ArgSpec& argSpec = seq.get_args()[ii]; + ASSERT_EQ(buf.serializeFrom(argSpec.get_argName()), Fw::SerializeStatus::FW_SERIALIZE_OK); + + ASSERT_EQ(buf.serializeFrom(argSpec.get_typeName()), Fw::SerializeStatus::FW_SERIALIZE_OK); + // Write size + ASSERT_EQ(buf.serializeFrom(argSpec.get_argSize()), Fw::SerializeStatus::FW_SERIALIZE_OK); } + + // Write statements for (U32 ii = 0; ii < seq.get_header().get_statementCount(); ii++) { ASSERT_EQ(buf.serializeFrom(seq.get_statements()[ii]), Fw::SerializeStatus::FW_SERIALIZE_OK); } @@ -104,6 +114,27 @@ void FpySequencerTester::removeFile(const char* name) { Os::FileSystem::removeFile(name); } +void FpySequencerTester::addArgumentSpec(Fw::String argName, Fw::String typeName, Fpy::StackSizeType argSize) { + U8 argCount = seq.get_header().get_argumentCount(); + FW_ASSERT(argCount < Fpy::MAX_SEQUENCE_ARG_COUNT); + + Fpy::ArgSpec& argSpec = seq.get_args()[argCount]; + + // Set arg_name + FW_ASSERT(argName.length() <= Fpy::MAX_ARG_SPEC_NAME_LEN); + argSpec.set_argName(argName); + + // Set type_name + FW_ASSERT(typeName.length() <= Fpy::MAX_ARG_SPEC_NAME_LEN); + argSpec.set_typeName(typeName); + + // Set size + argSpec.set_argSize(argSize); + + // Increment argument count + seq.get_header().set_argumentCount(++argCount); +} + void FpySequencerTester::resetRuntime() { // explicitly call dtor cmp.m_runtime.~Runtime(); @@ -635,6 +666,10 @@ void FpySequencerTester::tester_set_m_computedCRC(U32 crc) { this->cmp.m_computedCRC = crc; } +void FpySequencerTester::tester_set_m_sequenceArgs(Svc::SeqArgs args) { + this->cmp.m_sequenceArgs = args; +} + // Get cmp member pointers FpySequencer::Runtime* FpySequencerTester::tester_get_m_runtime_ptr() { return &(this->cmp.m_runtime); diff --git a/Svc/FpySequencer/test/ut/FpySequencerTester.hpp b/Svc/FpySequencer/test/ut/FpySequencerTester.hpp index ab3c76f7a0..fb2ff3ecde 100644 --- a/Svc/FpySequencer/test/ut/FpySequencerTester.hpp +++ b/Svc/FpySequencer/test/ut/FpySequencerTester.hpp @@ -79,6 +79,7 @@ class FpySequencerTester : public FpySequencerGTestBase, public ::testing::Test void writeToFile(const char* name, FwSizeType maxBytes = Fpy::Sequence::SERIALIZED_SIZE); void removeFile(const char* name); void addDirective(Fpy::DirectiveId id, Fw::StatementArgBuffer& buf); + void addArgumentSpec(Fw::String argName, Fw::String typeName, Fpy::StackSizeType argSize); void add_WAIT_REL(); void add_WAIT_REL(FpySequencer_WaitRelDirective dir); @@ -256,6 +257,7 @@ class FpySequencerTester : public FpySequencerGTestBase, public ::testing::Test Fw::Success tester_readBody(); Fw::Success tester_readHeader(); void tester_set_m_computedCRC(U32 crc); + void tester_set_m_sequenceArgs(Svc::SeqArgs args); Svc::FpySequencer::BreakpointInfo* tester_get_m_breakpoint_ptr(); Svc::Signal tester_checkStatementTimeout(); Svc::Signal tester_checkShouldWake();