diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..99bf475e5cfd40d9a84d7e21d28b1f0380870114 --- /dev/null +++ b/.clang-format @@ -0,0 +1,116 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: true + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Merge +IncludeCategories: + - Regex: '^"' + Priority: 2 + - Regex: '^<' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: ForIndentation +... + diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index f25060c0a0ccdd77d361f71cffaeeb85e993991b..684c966e49b30670fa8b7e5fc58997469b6e3ecc 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,7 +1,16 @@ <component name="ProjectCodeStyleConfiguration"> <code_scheme name="Project" version="173"> - <option name="RIGHT_MARGIN" value="100" /> <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" /> + <Objective-C> + <option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" /> + <option name="FUNCTION_PARAMETERS_ALIGN_MULTILINE" value="false" /> + <option name="FUNCTION_CALL_ARGUMENTS_ALIGN_MULTILINE" value="false" /> + <option name="SPACE_BEFORE_TEMPLATE_DECLARATION_LT" value="true" /> + <option name="SPACE_BEFORE_POINTER_IN_DECLARATION" value="false" /> + <option name="SPACE_AFTER_POINTER_IN_DECLARATION" value="true" /> + <option name="SPACE_BEFORE_REFERENCE_IN_DECLARATION" value="false" /> + <option name="SPACE_AFTER_REFERENCE_IN_DECLARATION" value="true" /> + </Objective-C> <Objective-C-extensions> <extensions> <pair source="cpp" header="hpp" fileNamingConvention="PASCAL_CASE" /> @@ -9,6 +18,12 @@ </extensions> </Objective-C-extensions> <codeStyleSettings language="ObjectiveC"> + <option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" /> + <option name="ALIGN_MULTILINE_FOR" value="false" /> + <option name="ALIGN_MULTILINE_ASSIGNMENT" value="false" /> + <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="false" /> + <option name="METHOD_CALL_CHAIN_WRAP" value="1" /> + <option name="ENUM_CONSTANTS_WRAP" value="2" /> <indentOptions> <option name="CONTINUATION_INDENT_SIZE" value="4" /> <option name="USE_TAB_CHARACTER" value="true" /> diff --git a/CMakeLists.txt b/CMakeLists.txt index 26659a90aa7cb5a617f8a27eafd2b1e6d9c85195..37b151c03e16be403d2d367a27ca5daffbd2b092 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,10 @@ add_library(common OBJECT src/Services/RequestVerificationService.cpp src/Services/TestService.cpp src/Services/TimeManagementService.cpp - src/Services/EventActionService.cpp + src/Services/LargePacketTransferService.cpp + src/Services/EventActionService.cpp + src/Services/TimeBasedSchedulingService.cpp + src/Services/FunctionManagementService.cpp ) # Specify the .cpp files for the executables diff --git a/ci/clang-format.sh b/ci/clang-format.sh new file mode 100755 index 0000000000000000000000000000000000000000..d8718f524a3230ced99febabba5cdd08b1e72bf0 --- /dev/null +++ b/ci/clang-format.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# +# Edit files, performing code style corrections using clang-format +# +# Usage: +# $ ci/clang-format.sh +# + +echo -e "\033[0;34mRunning clang-format...\033[0m" + +cd "$(dirname "$0")" +clang-format-7 -i `find ../src/ ../inc/ ../test/ -type f -regextype posix-egrep -regex '.*\.(cpp|hpp|c|h)'` \ + -verbose $@ diff --git a/ci/vera.sh b/ci/vera.sh index 470c348bf7e4d999de64b3e58df311a444c87bcc..4481cfb2b2b49d904725a211d4c58b51fe4c4048 100755 --- a/ci/vera.sh +++ b/ci/vera.sh @@ -10,4 +10,4 @@ echo -e "\033[0;34mRunning vera++...\033[0m" cd "$(dirname "$0")/.." -vera++ --error --profile custom `find src inc test -type f -regextype posix-egrep -regex '.*\.(cpp|hpp|c|h)'` +vera++ --error --parameter max-line-length=120 --profile custom `find src inc test -type f -regextype posix-egrep -regex '.*\.(cpp|hpp|c|h)'` diff --git a/inc/ECSS_Definitions.hpp b/inc/ECSS_Definitions.hpp index 1f000979a32969565a7cdee0d7e91f63779da207..047db0f70cda0a66d4069ffb7aacd3c780e45baf 100644 --- a/inc/ECSS_Definitions.hpp +++ b/inc/ECSS_Definitions.hpp @@ -5,13 +5,47 @@ #define ECSS_MAX_STRING_SIZE 256 +#define ECSS_MAX_FIXED_OCTET_STRING_SIZE 256 // For the ST13 large packet transfer service + // 7.4.1 #define CCSDS_PACKET_VERSION 0 // 7.4.4.1c -#define ECSS_PUS_VERSION 2 +#define ECSS_PUS_VERSION 2U // 9.3.1a.1.e #define ECSS_SEQUENCE_FLAGS 0x3 +/** + * @brief Maximum number of TC requests that can be contained in a single message request + * @details This definition accounts for the maximum number of TC packet requests that can be + * contained in the message of a request. This was defined for the time based command scheduling + * service and specifically to address the needs of the sub-services containing a TC packet in + * their message request. + * @attention This definition is probably dependent on the ECSS_TC_REQUEST_STRING_SIZE + */ +#define ECSS_MAX_REQUEST_COUNT 20 // todo: Needs to be defined + +/** + * @brief Maximum length of a String converted TC packet message + * @details This definition refers to the maximum length that an embedded TC packet, meaning a TC + * packet contained in a message request as a part of the request. + */ +#define ECSS_TC_REQUEST_STRING_SIZE 64 + +// todo: Define the maximum number of activities +#define ECSS_MAX_NUMBER_OF_TIME_SCHED_ACTIVITIES 10 + +/** + * @brief Time margin used in the time based command scheduling service ST[11] + * @details This defines the time margin in seconds, from the current rime, that an activity must + * have in order + * @todo Define the time margin for the command activation + */ +#define ECSS_TIME_MARGIN_FOR_ACTIVATION 60 + +// todo: Define the maximum delta between the specified +#define ECSS_MAX_DELTA_OF_RELEASE_TIME 60 +// release time and the actual release time + #endif //ECSS_SERVICES_ECSS_DEFINITIONS_H diff --git a/inc/ErrorHandler.hpp b/inc/ErrorHandler.hpp index 9864f708b31bc297459627581f2542bb4352fb4e..4f50837588ce77417bc2818c2093b675a9bfd267 100644 --- a/inc/ErrorHandler.hpp +++ b/inc/ErrorHandler.hpp @@ -69,7 +69,11 @@ public: /** * A function received a Message that was not of the correct type */ - OtherMessageType = 9, + OtherMessageType = 9, + /** + * Attempt to insert new function in a full function map (ST[08]) + */ + FunctionMapFull = 10, }; /** @@ -106,6 +110,8 @@ public: */ enum ExecutionStartErrorType { UnknownExecutionStartError = 0, + SubServiceExecutionStartError = 1, + InstructionExecutionStartError = 2, }; /** diff --git a/inc/Helpers/CRCHelper.hpp b/inc/Helpers/CRCHelper.hpp index 12da516b2d6718d6743b17779c2fcf01dec178cd..aa7149846b762498ac4e13979afbbe215119b3bd 100644 --- a/inc/Helpers/CRCHelper.hpp +++ b/inc/Helpers/CRCHelper.hpp @@ -6,7 +6,7 @@ class CRCHelper { /** - * CRC32 calculation helper class + * CRC16 calculation helper class * This class declares a function which calculates the CRC16 checksum of the given data. * * For now the actual implementation is the CRC16/CCITT variant (ECSS-E-ST-70-41C, pg.615) @@ -25,7 +25,7 @@ public: * Actual CRC calculation function. * @param message (pointer to the data to be checksummed) * @param length (size in bytes) - * @return the CRC32 checksum of the input data + * @return the CRC16 checksum of the input data */ static uint16_t calculateCRC(const uint8_t* message, uint32_t length); diff --git a/inc/Message.hpp b/inc/Message.hpp index 3240626189f8848a4147f9e97e9d46399dfaa5fe..ac1509f9ea39dc2c8d330e0ebd4bcc34978c382a 100644 --- a/inc/Message.hpp +++ b/inc/Message.hpp @@ -22,6 +22,41 @@ class Message { public: Message() = default; + /** + * @brief Compare two messages + * @details Check whether two Message objects are of the same type + * @param msg1 First message for comparison + * @param msg2 Second message for comparison + * @return A boolean value indicating whether the messages are of the same type + */ + static bool isSameType(const Message &msg1, const Message &msg2) { + return (msg1.packetType == msg2.packetType) && + (msg1.messageType == msg2.messageType) && (msg1.serviceType == msg2.serviceType); + } + + /** + * @brief Overload the equality operator to compare messages + * @details Compare two @ref ::Message objects, based on their contents and type + * @param The message content to compare against + * @todo Activate the dataSize check when the Message object data field is defined with a + * fixed size + * @return The result of comparison + */ + bool operator==(const Message &msg) const { + // todo: Enable the following check when the message data field has a fixed size padded + // with zeros. At the moment the data array is not padded with zeros to fulfil the + // maximum set number of a TC request message. + //if (this->dataSize != msg.dataSize) return false; + + for (uint16_t i = 0; i < ECSS_MAX_MESSAGE_SIZE; i++) { + if (this->data[i] != msg.data[i]) { + return false; + } + } + return (this->packetType == msg.packetType) && + (this->messageType == msg.messageType) && (this->serviceType == msg.serviceType); + } + enum PacketType { TM = 0, // Telemetry TC = 1 // Telecommand @@ -438,6 +473,16 @@ public: return size; // Return the string size } + /** + * @brief Skip read bytes in the read string + * @details Skips the provided number of bytes, by incrementing the readPosition and this is + * done to avoid accessing the `readPosition` variable directly + * @param numberOfBytes The number of bytes to be skipped + */ + void skipBytes(uint16_t numberOfBytes) { + readPosition += numberOfBytes; + } + /** * Reset the message reading status, and start reading data from it again */ diff --git a/inc/MessageParser.hpp b/inc/MessageParser.hpp index 4d1985b274f049dd325287de6e623b9a85b0ea4b..4b848bc75c08f443d8a1302d4c683baf2b4abc01 100644 --- a/inc/MessageParser.hpp +++ b/inc/MessageParser.hpp @@ -37,7 +37,24 @@ public: * this great analysis: * stackoverflow.com/questions/15078638/can-i-turn-unsigned-char-into-char-and-vice-versa */ - Message parseRequestTC(String<ECSS_EVENT_SERVICE_STRING_SIZE> data); + Message parseRequestTC(String<ECSS_TC_REQUEST_STRING_SIZE> data); + + /** + * @brief Overloaded version + * @param data A uint8_t array of the TC packet data + * @return Parsed message + */ + Message parseRequestTC(uint8_t* data); + + /** + * @brief Converts a TC packet of type Message to a String + * @details Convert a parsed TC message to a string in order to be used by the services + * @param message The Message object to be parsed to a String + * @return A String class containing the parsed TC request + * @attention The returned String has a fixed size, therefore the message size is considered + * fixed and equal to the ECSS_TC_REQUEST_STRING_SIZE definition. + */ + String<ECSS_TC_REQUEST_STRING_SIZE> convertTCToStr(Message& message); private: /** @@ -49,7 +66,7 @@ private: * @param length The size of the header * @param message The Message to modify based on the header */ - void parseTC(uint8_t *data, uint16_t length, Message &message); + void parseTC(const uint8_t *data, uint16_t length, Message &message); /** * Parse the ECSS Telemetry packet secondary header @@ -60,7 +77,7 @@ private: * @param length The size of the header * @param message The Message to modify based on the header */ - void parseTM(uint8_t *data, uint16_t length, Message &message); + void parseTM(const uint8_t *data, uint16_t length, Message &message); }; diff --git a/inc/Platform/x86/TimeGetter.hpp b/inc/Platform/x86/TimeGetter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7dcf22ed020dd6163ef5c1adede6e1f3dbf26d22 --- /dev/null +++ b/inc/Platform/x86/TimeGetter.hpp @@ -0,0 +1,24 @@ +#ifndef ECSS_SERVICES_TIMEGETTER_HPP +#define ECSS_SERVICES_TIMEGETTER_HPP + +#include <iostream> +#include <cstdint> +#include <ctime> + + +/** + * @brief Get the current time + */ +class TimeGetter { +public: + + /** + * @brief Gets the current time in UNIX epoch + * @return Current UNIX epoch time, in elapsed seconds + */ + static inline uint32_t getSeconds() { + return static_cast<uint32_t >(time(nullptr)); + } +}; + +#endif //ECSS_SERVICES_TIMEGETTER_HPP diff --git a/inc/ServicePool.hpp b/inc/ServicePool.hpp index 8a1e6035855eea8b35003ff37047ac4bb1b01f06..d8ec83c84eef6be474397028868a89eef78f9d60 100644 --- a/inc/ServicePool.hpp +++ b/inc/ServicePool.hpp @@ -1,6 +1,7 @@ #ifndef ECSS_SERVICES_SERVICEPOOL_HPP #define ECSS_SERVICES_SERVICEPOOL_HPP +#include "Services/LargePacketTransferService.hpp" #include "Services/RequestVerificationService.hpp" #include "Services/TimeManagementService.hpp" #include "Services/EventReportService.hpp" @@ -8,6 +9,7 @@ #include "Services/ParameterService.hpp" #include "Services/TestService.hpp" #include "Services/MemoryManagementService.hpp" +#include "Services/FunctionManagementService.hpp" /** * Defines a class that contains instances of all Services. @@ -25,6 +27,8 @@ public: EventActionService eventAction; TestService testService; ParameterService parameterManagement; + LargePacketTransferService largePacketTransferService; + FunctionManagementService functionManagement; /** * The default ServicePool constructor diff --git a/inc/Services/EventActionService.hpp b/inc/Services/EventActionService.hpp index 338a67345c0163bb8df8708f784c0b7aae868edc..3b561617d2cbe76b4423b6bcc83035c3fc5af79e 100644 --- a/inc/Services/EventActionService.hpp +++ b/inc/Services/EventActionService.hpp @@ -1,7 +1,6 @@ #ifndef ECSS_SERVICES_EVENTACTIONSERVICE_HPP #define ECSS_SERVICES_EVENTACTIONSERVICE_HPP -#define ECSS_EVENT_SERVICE_STRING_SIZE 64 #define ECSS_EVENT_ACTION_STRUCT_ARRAY_SIZE 256 diff --git a/inc/Services/EventReportService.hpp b/inc/Services/EventReportService.hpp index 0ecaf179e3468712fe49984cc3287fc643e32ee9..fdb32c0c5b9ea143c031d1efeaad730f5cd8e822 100644 --- a/inc/Services/EventReportService.hpp +++ b/inc/Services/EventReportService.hpp @@ -2,7 +2,7 @@ #define ECSS_SERVICES_EVENTREPORTSERVICE_HPP #include "Service.hpp" -#include <bitset> +#include <etl/bitset.h> /** * Implementation of ST[05] event reporting service * @@ -17,7 +17,7 @@ class EventReportService : public Service { private: static const uint16_t numberOfEvents = 7; - std::bitset<numberOfEvents> stateOfEvents; + etl::bitset<numberOfEvents> stateOfEvents; public: // Variables that count the event reports per severity level uint16_t lowSeverityReportCount; @@ -170,7 +170,7 @@ public: * Getter for stateOfEvents bitset * @return stateOfEvents, just in case the whole bitset is needed */ - std::bitset<numberOfEvents> getStateOfEvents() { + etl::bitset<numberOfEvents> getStateOfEvents() { return stateOfEvents; } }; diff --git a/inc/Services/FunctionManagementService.hpp b/inc/Services/FunctionManagementService.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2267f7ea112afabefbb97adce73d4844ad40a883 --- /dev/null +++ b/inc/Services/FunctionManagementService.hpp @@ -0,0 +1,95 @@ +#ifndef ECSS_SERVICES_FUNCTIONMANAGEMENTSERVICE_HPP +#define ECSS_SERVICES_FUNCTIONMANAGEMENTSERVICE_HPP + +#include "etl/map.h" +#include "etl/String.hpp" +#include "Message.hpp" +#include "Service.hpp" +#include "ErrorHandler.hpp" + +#define FUNC_MAP_SIZE 5 // size of the function map (number of elements) +#define FUNC_NAME_LENGTH 32 // max length of the function name +#define MAX_ARG_LENGTH 32 // maximum argument byte string length + +/** + * Implementation of the ST[08] function management service + * + * This class implements a skeleton framework for the ST[08] service as described in + * ECSS-E-ST-70-41C, pages 157-159. Final implementation is dependent on subsystem requirements + * which are, as of this writing, undefined yet. + * + * Caveats: + * 1) Function names shall be exactly MAXFUNCNAMELENGTH-lengthed in order to be properly read + * and stored! (not sure if this is a caveat though, as ECSS-E-ST-70-41C stipulates for ST[08] + * that all function names must be fixed-length character strings) + * + * You have been warned. + * + * @author Grigoris Pavlakis <grigpavl@ece.auth.gr> + */ + + /** + * Usage of the include() function: + * + * @code + * void foo(String<MAX_ARG_LENGTH> b) { + * std::cout << "SPAAAACE!" << std::endl; + * } + * + * void bar(String<MAX_ARG_LENGTH> b) { + * std::cout << "I HAZ A CUBESAT THAT SNAPS PIX!" << std::endl; + * } + * + * void baz(String<MAX_ARG_LENGTH> b) { + * std::cout << "QWERTYUIOP" << std::endl; + * } + * + * FunctionManagementService::FunctionManagementService() { + * include(String<FUNC_NAME_LENGTH>("foo"), &foo); + * include(String<FUNC_NAME_LENGTH>("bar"), &bar); + * include(String<FUNC_NAME_LENGTH>("baz"), &baz); + * } + */ + +typedef String<FUNC_NAME_LENGTH> functionName; +typedef etl::map<functionName, void(*)(String<MAX_ARG_LENGTH>), FUNC_MAP_SIZE> +FunctionMap; + +class FunctionManagementService : public Service { + /** + * Map of the function names to their respective pointers. Size controlled by FUNC_MAP_SIZE + */ + FunctionMap funcPtrIndex; + + +public: + /** + * Constructs the function pointer index with all the necessary functions at initialization time + * These functions need to be in scope. Un-default when needed. + * + * @param None + */ + FunctionManagementService() = default; + + /** + * Calls the function described in the TC[8,1] message *msg*, passing the arguments contained + * and, if non-existent, generates a failed start of execution notification. Returns an unneeded + * int, for testing purposes. + * @param msg A TC[8,1] message + */ + void call(Message& msg); + + /** + * Includes a new function in the pointer map. This enables it to be called by way of a valid + * TC [8,1] message. + * + * @param funcName the function's name. Max. length is FUNC_NAME_LENGTH bytes. + * @param ptr pointer to a function of void return type and a MAX_ARG_LENGTH-lengthed byte + * string as argument (which contains the actual arguments of the function) + */ + void include(String<FUNC_NAME_LENGTH> funcName, void(*ptr)(String<MAX_ARG_LENGTH>)); + + int getMapSize() { return funcPtrIndex.size(); } +}; + +#endif //ECSS_SERVICES_FUNCTIONMANAGEMENTSERVICE_HPP diff --git a/inc/Services/LargePacketTransferService.hpp b/inc/Services/LargePacketTransferService.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7ed7d997ebcea0009193ebeb52d823001c38cab2 --- /dev/null +++ b/inc/Services/LargePacketTransferService.hpp @@ -0,0 +1,82 @@ +#ifndef ECSS_SERVICES_LARGEPACKETTRANSFERSERVICE_HPP +#define ECSS_SERVICES_LARGEPACKETTRANSFERSERVICE_HPP + +#include "Service.hpp" +#include <etl/String.hpp> + +/** + * Implementation of the ST[13] large packet transfer service + * The goal of this service is to help in splitting data packages that exceed the standard's + * maximum data size + * + * Note: More information can be found in the standards' manual, in p. 526-528 and in p. 229-236 + */ + +class LargePacketTransferService : public Service { +public: + + /** + * Default constructor since only functions will be used. + */ + LargePacketTransferService() { + serviceType = 13; + } + + /** + * Function that handles the first part of the download report + * @param largeMessageTransactionIdentifier The identifier of the large packet + * @param partSequenceNumber The identifier of the part of the large packet + * @param string The data contained in this part of the large packet + */ + void firstDownlinkPartReport(uint16_t + largeMessageTransactionIdentifier, uint16_t partSequenceNumber, + const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string); + + /** + * Function that handles the n-2 parts of tbe n-part download report + * @param largeMessageTransactionIdentifier The identifier of the large packet + * @param partSequenceNumber The identifier of the part of the large packet + * @param string The data contained in this part of the large packet + */ + void intermediateDownlinkPartReport(uint16_t + largeMessageTransactionIdentifier, + uint16_t partSequenceNumber, + const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string); + + /** + * Function that handles the last part of the download report + * @param largeMessageTransactionIdentifier The identifier of the large packet + * @param partSequenceNumber The identifier of the part of the large packet + * @param string The data contained in this part of the large packet + */ + void lastDownlinkPartReport(uint16_t + largeMessageTransactionIdentifier, uint16_t partSequenceNumber, + const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string); + + + // The three uplink functions should handle a TC request to "upload" data. Since there is not + // a createTC function ready, I just return the given string. + // @TODO: Modify these functions properly + /** + * Function that handles the first part of the uplink request + * @param string This will change when these function will be modified + */ + String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> + firstUplinkPart(const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string); + + /** + * Function that handles the n-2 parts of tbe n-part uplink request + * @param string This will change when these function will be modified + */ + String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> + intermediateUplinkPart(const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string); + + /** + * Function that handles the last part of the uplink request + * @param string This will change when these function will be modified + */ + String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> + lastUplinkPart(const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string); +}; + +#endif // ECSS_SERVICES_LARGEPACKETTRANSFERSERVICE_HPP diff --git a/inc/Services/TimeBasedSchedulingService.hpp b/inc/Services/TimeBasedSchedulingService.hpp new file mode 100644 index 0000000000000000000000000000000000000000..76343b5d76fa5aac07e9a59f29899ec00769e4bd --- /dev/null +++ b/inc/Services/TimeBasedSchedulingService.hpp @@ -0,0 +1,250 @@ +#ifndef ECSS_SERVICES_TIMEBASEDSCHEDULINGSERVICE_HPP +#define ECSS_SERVICES_TIMEBASEDSCHEDULINGSERVICE_HPP + +#include "etl/list.h" +#include "Service.hpp" +#include "ErrorHandler.hpp" +#include "MessageParser.hpp" +#include "Helpers/CRCHelper.hpp" +#include "Helpers/TimeHelper.hpp" + +// Include platform specific files +#include "Platform/x86/TimeGetter.hpp" + +/** + * @def SUB_SCHEDULES_ENABLED + * @brief Indicates whether sub-schedules are supported + * + * @details Sub-schedules are currently not implemented so this has no effect + */ +/** + * @def GROUPS_ENABLED + * @brief Indicates whether scheduling groups are enabled + */ +#define GROUPS_ENABLED 0 +#define SUB_SCHEDULES_ENABLED 0 + + +/** + * @brief Namespace to access private members during test + * + * @details Define a namespace for the access of the private members to avoid conflicts + */ +namespace unit_test { + struct Tester; +} // namespace unit_test + +/** + * @brief An implementation of the ECSS standard ST[11] service + * + * @details This service is taking care of the timed release of a received TC packet from the + * ground. + * @todo Define whether the parsed absolute release time is saved in the scheduled activity as an + * uint32_t or in the time format specified by the time management service. + */ +class TimeBasedSchedulingService : public Service { +private: + /** + * @brief Indicator of the schedule execution + * + * @details The schedule execution indicator will be updated by the process that is running + * the time scheduling service. + */ + bool executionFunctionStatus = false; // True indicates "enabled" and False "disabled" state + + MessageParser msgParser; // Parse TC packets + + /** + * @brief Request identifier of the received packet + * + * @details The request identifier consists of the application process ID, the packet + * sequence count and the source ID, all defined in the ECSS standard. + * @var applicationID Application process ID + * @var sequenceCount Packet sequence count + * @var sourceID Packet source ID + */ + struct RequestID { + uint16_t applicationID = 0; + uint16_t sequenceCount = 0; + uint8_t sourceID = 0; + + bool operator!=(const RequestID &rightSide) const { + return (sequenceCount != rightSide.sequenceCount) or + (applicationID != rightSide.applicationID) or (sourceID != rightSide.sourceID); + } + }; + + /** + * @brief Instances of activities to run in the schedule + * + * @details All scheduled activities must contain the request they exist for, their release + * time and the corresponding request identifier. + * @var request Contains the received TC request + * @var requestID Contains the unique request identifier for that activity + * @var requestReleaseTime The absolute time is seconds of the request release + */ + struct ScheduledActivity { + Message request; // Hold the received command request + RequestID requestID; // Request ID, characteristic of the definition + uint32_t requestReleaseTime = 0; // Keep the command release time + // todo: If we decide to use sub-schedules, the ID of that has to be defined + // todo: If groups are used, then the group ID has to be defined here + }; + + /** + * @brief Hold the scheduled activities + * + * @details The scheduled activities in this list are ordered by their release time, as the + * standard requests. + */ + etl::list<ScheduledActivity, ECSS_MAX_NUMBER_OF_TIME_SCHED_ACTIVITIES> scheduledActivities; + + + /** + * @brief Sort the activities by their release time + * + * @details The ECSS standard requires that the activities are sorted in the TM message + * response. Also it is better to have the activities sorted. + */ + inline void sortActivitiesReleaseTime(etl::list<ScheduledActivity, + ECSS_MAX_NUMBER_OF_TIME_SCHED_ACTIVITIES> &schedActivities) { + schedActivities.sort([](ScheduledActivity const &leftSide, ScheduledActivity const + &rightSide) { return leftSide.requestReleaseTime < rightSide.requestReleaseTime; }); + } + + /** + * @brief Define a friend in order to be able to access private members during testing + * + * @details The private members defined in this class, must not in any way be public to avoid + * misuse. During testing, access to private members for verification is required, so an + * access friend structure is defined here. + */ + friend struct ::unit_test::Tester; + + +public: + /** + * @brief Class constructor + * @details Initializes the serviceType + */ + TimeBasedSchedulingService(); + + /** + * @brief TC[11,1] enable the time-based schedule execution function + * + * @details Enables the time-based command execution scheduling + * @param request Provide the received message as a parameter + */ + void enableScheduleExecution(Message &request); + + /** + * @breif TC[11,2] disable the time-based schedule execution function + * + * @details Disables the time-based command execution scheduling + * @param request Provide the received message as a parameter + */ + void disableScheduleExecution(Message &request); + + /** + * @brief TC[11,3] reset the time-based schedule + * + * @details Resets the time-based command execution schedule, by clearing all scheduled + * activities. + * @param request Provide the received message as a parameter + */ + void resetSchedule(Message &request); + + /** + * @brief TC[11,4] insert activities into the time based schedule + * + * @details Add activities into the schedule for future execution. The activities are inserted + * by ascending order of their release time. This done to avoid confusion during the + * execution of the schedule and also to make things easier whenever a release time sorted + * report is requested by he corresponding service. + * @param request Provide the received message as a parameter + * @todo Definition of the time format is required + * @throws ExecutionStartError If there is request to be inserted and the maximum + * number of activities in the current schedule has been reached, then an @ref + * ErrorHandler::ExecutionStartErrorType is being issued. Also if the release time of the + * request is less than a set time margin, defined in @ref ECSS_TIME_MARGIN_FOR_ACTIVATION, + * from the current time a @ref ErrorHandler::ExecutionStartErrorType is also issued. + */ + void insertActivities(Message &request); + + /** + * @brief TC[11,15] time-shift all scheduled activities + * + * @details All scheduled activities are shifted per user request. The relative time offset + * received and tested against the current time. + * @param request Provide the received message as a parameter + * @todo Definition of the time format is required for the relative time format + * @throws ExecutionStartError If the release time of the request is less than a + * set time margin, defined in @ref ECSS_TIME_MARGIN_FOR_ACTIVATION, from the current time an + * @ref ErrorHandler::ExecutionStartErrorType report is issued for that instruction. + */ + void timeShiftAllActivities(Message &request); + + /** + * @brief TC[11,16] detail-report all activities + * + * @details Send a detailed report about the status of all the activities + * on the current schedule. Generates a TM[11,10] response. + * @param request Provide the received message as a parameter + * @todo Replace the time parsing with the time parser + */ + void detailReportAllActivities(Message &request); + + /** + * @brief TC[11,9] detail-report activities identified by request identifier + * + * @details Send a detailed report about the status of the requested activities, based on the + * provided request identifier. Generates a TM[11,10] response. The matched activities are + * contained in the report, in an ascending order based on their release time. + * @param request Provide the received message as a parameter + * @todo Replace time parsing with the time parser + * @throws ExecutionStartError If a requested activity, identified by the provided + * request identifier is not found in the schedule issue an @ref + * ErrorHandler::ExecutionStartErrorType for that instruction. + */ + void detailReportActivitiesByID(Message &request); + + /** + * @brief TC[11,12] summary-report activities identified by request identifier + * + * @details Send a summary report about the status of the requested activities. Generates a + * TM[11,13] response, with activities ordered in an ascending order, based on their release + * time. + * @param request Provide the received message as a parameter + * @throws ExecutionStartError If a requested activity, identified by the provided + * request identifier is not found in the schedule issue an @ref + * ErrorHandler::ExecutionStartErrorType for that instruction. + */ + void summaryReportActivitiesByID(Message &request); + + /** + * @brief TC[11,5] delete time-based scheduled activities identified by a request identifier + * + * @details Delete certain activities by using the unique request identifier. + * @param request Provide the received message as a parameter + * @throws ExecutionStartError If a requested activity, identified by the provided + * request identifier is not found in the schedule issue an @ref + * ErrorHandler::ExecutionStartErrorType for that instruction. + */ + void deleteActivitiesByID(Message &request); + + /** + * @brief TC[11,7] time-shift scheduled activities identified by a request identifier + * + * @details Time-shift certain activities by using the unique request identifier + * @param request Provide the received message as a parameter + * @todo Definition of the time format is required + * @throws ExecutionStartError If the requested time offset is less than the earliest + * time from the currently scheduled activities plus the @ref ECSS_TIME_MARGIN_FOR_ACTIVATION, + * then the request is rejected and an @ref ErrorHandler::ExecutionStartErrorType is issued. + * Also if an activity with a specified request identifier is not found, generate a failed + * start of execution for that specific instruction. + */ + void timeShiftActivitiesByID(Message &request); +}; + +#endif //ECSS_SERVICES_TIMEBASEDSCHEDULINGSERVICE_HPP diff --git a/inc/etl/String.hpp b/inc/etl/String.hpp index 8fbd401cc4eb033cce095feb13d4fa577aa7d013..f9198df199888453fa690067020528ce9c2d1a36 100644 --- a/inc/etl/String.hpp +++ b/inc/etl/String.hpp @@ -1,7 +1,6 @@ #ifndef ECSS_SERVICES_ETL_STRING_HPP #define ECSS_SERVICES_ETL_STRING_HPP - #include <cstddef> #include <etl/cstring.h> diff --git a/lib/Catch2 b/lib/Catch2 index d63307279412de3870cf97cc6802bae8ab36089e..62460fafe6b54c3173bc5cbc46d05a5f071017ff 160000 --- a/lib/Catch2 +++ b/lib/Catch2 @@ -1 +1 @@ -Subproject commit d63307279412de3870cf97cc6802bae8ab36089e +Subproject commit 62460fafe6b54c3173bc5cbc46d05a5f071017ff diff --git a/src/Message.cpp b/src/Message.cpp index 409e65e4e18ae303c8cbcac02422172615943184..0127ecf679d0bdf93600413653eea9878f20690e 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -145,6 +145,7 @@ void Message::readString(uint8_t *string, uint16_t size) { ASSERT_REQUEST(size < ECSS_MAX_STRING_SIZE, ErrorHandler::StringTooShort); memcpy(string, data + readPosition, size); + string[size] = '\0'; // todo: Use that for now to avoid problems. Later to be removed readPosition += size; } diff --git a/src/MessageParser.cpp b/src/MessageParser.cpp index af4988a1a302b2da35eed9d2cbe45e6b1508b706..d382dd3432bc10b1dbe1b097db8afec2a56827ef 100644 --- a/src/MessageParser.cpp +++ b/src/MessageParser.cpp @@ -1,4 +1,3 @@ -#include <cstring> #include <Services/EventActionService.hpp> #include <ServicePool.hpp> #include "ErrorHandler.hpp" @@ -53,7 +52,7 @@ Message MessageParser::parse(uint8_t *data, uint32_t length) { return message; } -void MessageParser::parseTC(uint8_t *data, uint16_t length, Message &message) { +void MessageParser::parseTC(const uint8_t *data, uint16_t length, Message &message) { ErrorHandler::assertRequest(length >= 5, message, ErrorHandler::UnacceptableMessage); // Individual fields of the TC header @@ -61,6 +60,8 @@ void MessageParser::parseTC(uint8_t *data, uint16_t length, Message &message) { uint8_t serviceType = data[1]; uint8_t messageType = data[2]; + // todo: Fix this parsing function, because it assumes PUS header in data, which is not true + // with the current implementation ErrorHandler::assertRequest(pusVersion == 2, message, ErrorHandler::UnacceptableMessage); // Remove the length of the header @@ -74,15 +75,34 @@ void MessageParser::parseTC(uint8_t *data, uint16_t length, Message &message) { message.dataSize = length; } -Message MessageParser::parseRequestTC(String<ECSS_EVENT_SERVICE_STRING_SIZE> data) { +Message MessageParser::parseRequestTC(String<ECSS_TC_REQUEST_STRING_SIZE> data) { Message message; auto *dataInt = reinterpret_cast<uint8_t *>(data.data()); message.packetType = Message::TC; - parseTC(dataInt, ECSS_EVENT_SERVICE_STRING_SIZE, message); + parseTC(dataInt, ECSS_TC_REQUEST_STRING_SIZE, message); return message; } -void MessageParser::parseTM(uint8_t *data, uint16_t length, Message &message) { +Message MessageParser::parseRequestTC(uint8_t* data) { + Message message; + message.packetType = Message::TC; + parseTC(data, ECSS_TC_REQUEST_STRING_SIZE, message); + return message; +} + +String<ECSS_TC_REQUEST_STRING_SIZE> MessageParser::convertTCToStr(Message &message) { + uint8_t tempString[ECSS_TC_REQUEST_STRING_SIZE] = {0}; + + tempString[0] = ECSS_PUS_VERSION << 4; // Assign the pusVersion = 2 + tempString[1] = message.serviceType; + tempString[2] = message.messageType; + memcpy(tempString + 5, message.data, ECSS_TC_REQUEST_STRING_SIZE - 5); + String<ECSS_TC_REQUEST_STRING_SIZE> dataString(tempString); + + return dataString; +} + +void MessageParser::parseTM(const uint8_t *data, uint16_t length, Message &message) { ErrorHandler::assertRequest(length >= 5, message, ErrorHandler::UnacceptableMessage); // Individual fields of the TM header diff --git a/src/Service.cpp b/src/Service.cpp new file mode 100644 index 0000000000000000000000000000000000000000..66fdd045bb9f777cea939f8c32fb344a995263a5 --- /dev/null +++ b/src/Service.cpp @@ -0,0 +1 @@ +#include "Service.hpp" diff --git a/src/Services/EventActionService.cpp b/src/Services/EventActionService.cpp index 2ace42dd9796cfff95120b189ed95e35e44c9722..6ac29c0531184da62bbd617f94189fb240995cf6 100644 --- a/src/Services/EventActionService.cpp +++ b/src/Services/EventActionService.cpp @@ -34,12 +34,12 @@ void EventActionService::addEventActionDefinitions(Message message) { eventActionDefinitionArray[index].enabled = true; eventActionDefinitionArray[index].applicationId = applicationID; eventActionDefinitionArray[index].eventDefinitionID = eventDefinitionID; - if (message.dataSize - 4 > ECSS_EVENT_SERVICE_STRING_SIZE) { + if (message.dataSize - 4 > ECSS_TC_REQUEST_STRING_SIZE) { ErrorHandler::reportInternalError(ErrorHandler::InternalErrorType::MessageTooLarge); } else { - char data[ECSS_EVENT_SERVICE_STRING_SIZE]; + char data[ECSS_TC_REQUEST_STRING_SIZE]; message.readString(data, message.dataSize - 4); - eventActionDefinitionArray[index].request = String<ECSS_EVENT_SERVICE_STRING_SIZE>( + eventActionDefinitionArray[index].request = String<ECSS_TC_REQUEST_STRING_SIZE>( data); } } @@ -150,11 +150,11 @@ void EventActionService::eventActionStatusReport() { } } report.appendUint8(count); - for (uint16_t i = 0; i < ECSS_EVENT_ACTION_STRUCT_ARRAY_SIZE; i++) { - if (not eventActionDefinitionArray[i].empty) { - report.appendEnum16(eventActionDefinitionArray[i].applicationId); - report.appendEnum16(eventActionDefinitionArray[i].eventDefinitionID); - report.appendBoolean(eventActionDefinitionArray[i].enabled); + for (const auto &definition : eventActionDefinitionArray) { + if (not definition.empty) { + report.appendEnum16(definition.applicationId); + report.appendEnum16(definition.eventDefinitionID); + report.appendBoolean(definition.enabled); } } storeMessage(report); @@ -178,13 +178,13 @@ void EventActionService::disableEventActionFunction(Message message) { void EventActionService::executeAction(uint16_t eventID) { // Custom function if (eventActionFunctionStatus) { - for (uint16_t i = 0; i < ECSS_EVENT_ACTION_STRUCT_ARRAY_SIZE; i++) { - if (not eventActionDefinitionArray[i].empty && - eventActionDefinitionArray[i].enabled) { - if (eventActionDefinitionArray[i].eventDefinitionID == eventID) { + for (const auto &definition : eventActionDefinitionArray) { + if (not definition.empty && + definition.enabled) { + if (definition.eventDefinitionID == eventID) { MessageParser messageParser; Message message = messageParser.parseRequestTC( - eventActionDefinitionArray[i].request); + definition.request); messageParser.execute(message); } } diff --git a/src/Services/FunctionManagementService.cpp b/src/Services/FunctionManagementService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8dcc602792b45199d17bf95e53330905ebcfaab0 --- /dev/null +++ b/src/Services/FunctionManagementService.cpp @@ -0,0 +1,52 @@ +#include "Services/FunctionManagementService.hpp" + +void FunctionManagementService::call(Message& msg) { + msg.resetRead(); + ErrorHandler::assertRequest(msg.packetType == Message::TC, msg, + ErrorHandler::AcceptanceErrorType::UnacceptableMessage); + ErrorHandler::assertRequest(msg.messageType == 1, msg, + ErrorHandler::AcceptanceErrorType::UnacceptableMessage); + ErrorHandler::assertRequest(msg.serviceType == 8, msg, + ErrorHandler::AcceptanceErrorType::UnacceptableMessage); + + uint8_t funcName[FUNC_NAME_LENGTH]; // the function's name + uint8_t funcArgs[MAX_ARG_LENGTH]; // arguments for the function + + msg.readString(funcName, FUNC_NAME_LENGTH); + msg.readString(funcArgs, MAX_ARG_LENGTH); + + if (msg.dataSize > FUNC_NAME_LENGTH + MAX_ARG_LENGTH) { + ErrorHandler::reportError(msg, + ErrorHandler::ExecutionStartErrorType::UnknownExecutionStartError); // report failed + // start of execution as requested by the standard + return; + } + + // locate the appropriate function pointer + String<FUNC_NAME_LENGTH> name(funcName); + FunctionMap::iterator iter = funcPtrIndex.find(name); + void (*selected)(String<MAX_ARG_LENGTH>); + + if (iter != funcPtrIndex.end()) { + selected = *iter->second; + } else { + ErrorHandler::reportError(msg, + ErrorHandler::ExecutionStartErrorType::UnknownExecutionStartError); + return; + } + + // execute the function if there are no obvious flaws (defined in the standard, pg.158) + selected(funcArgs); +} + +void FunctionManagementService::include(String<FUNC_NAME_LENGTH> funcName, void(*ptr) + (String<MAX_ARG_LENGTH>)) { + + if (not funcPtrIndex.full()) { // CAUTION: etl::map won't check by itself if it's full + // before attempting to insert a key-value pair, causing segmentation faults. Check first! + funcName.append(FUNC_NAME_LENGTH - funcName.length(), '\0'); + funcPtrIndex.insert(std::make_pair(funcName, ptr)); + } else { + ErrorHandler::reportInternalError(ErrorHandler::InternalErrorType::FunctionMapFull); + } +} diff --git a/src/Services/LargePacketTransferService.cpp b/src/Services/LargePacketTransferService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fedfec8f97b65dd9264502c8ef45c8e801e4e084 --- /dev/null +++ b/src/Services/LargePacketTransferService.cpp @@ -0,0 +1,55 @@ +#include <Services/LargePacketTransferService.hpp> +#include "Message.hpp" +#include <etl/String.hpp> + +void LargePacketTransferService::firstDownlinkPartReport(uint16_t + largeMessageTransactionIdentifier, + uint16_t partSequenceNumber, + const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string) { + // TM[13,1] + + Message report = createTM(1); + report.appendUint16(largeMessageTransactionIdentifier); // large message transaction identifier + report.appendUint16(partSequenceNumber); // part sequence number + report.appendOctetString(string); // fixed octet-string + storeMessage(report); +} + +void LargePacketTransferService::intermediateDownlinkPartReport(uint16_t + largeMessageTransactionIdentifier, uint16_t partSequenceNumber, const + String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string) { + // TM[13,2] + Message report = createTM(2); + report.appendUint16(largeMessageTransactionIdentifier); // large message transaction identifier + report.appendUint16(partSequenceNumber); // part sequence number + report.appendOctetString(string); // fixed octet-string + storeMessage(report); +} + +void LargePacketTransferService::lastDownlinkPartReport(uint16_t largeMessageTransactionIdentifier, + uint16_t partSequenceNumber, const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string) { + // TM[13,3] + Message report = createTM(3); + report.appendUint16(largeMessageTransactionIdentifier); // large message transaction identifier + report.appendUint16(partSequenceNumber); // part sequence number + report.appendOctetString(string); // fixed octet-string + storeMessage(report); +} + +String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> LargePacketTransferService::firstUplinkPart( + const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string) { + // TC[13,9] + return string; +} + +String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> LargePacketTransferService::intermediateUplinkPart( + const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string) { + // TC[13,10] + return string; +} + +String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> +LargePacketTransferService::lastUplinkPart(const String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> &string) { + // TC[13, 11] + return string; +} diff --git a/src/Services/TimeBasedSchedulingService.cpp b/src/Services/TimeBasedSchedulingService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a4b74861f91167a5dbbcd942daa51405e66265d5 --- /dev/null +++ b/src/Services/TimeBasedSchedulingService.cpp @@ -0,0 +1,283 @@ +#include "Services/TimeBasedSchedulingService.hpp" + + +TimeBasedSchedulingService::TimeBasedSchedulingService() { + serviceType = 11; +} + +void TimeBasedSchedulingService::enableScheduleExecution(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 1); + + executionFunctionStatus = true; // Enable the service +} + +void TimeBasedSchedulingService::disableScheduleExecution(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 2); + + executionFunctionStatus = false; // Disable the service +} + +void TimeBasedSchedulingService::resetSchedule(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 3); + + executionFunctionStatus = false; // Disable the service + scheduledActivities.clear(); // Delete all scheduled activities + // todo: Add resetting for sub-schedules and groups, if defined +} + +void TimeBasedSchedulingService::insertActivities(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 4); + + // todo: Get the sub-schedule ID if they are implemented + uint16_t iterationCount = request.readUint16(); // Get the iteration count, (N) + while (iterationCount--) { + // todo: Get the group ID first, if groups are used + uint32_t currentTime = TimeGetter::getSeconds(); // Get the current system time + + uint32_t releaseTime = request.readUint32(); // Get the specified release time + if ((not scheduledActivities.available()) || + (releaseTime < (currentTime + ECSS_TIME_MARGIN_FOR_ACTIVATION))) { + ErrorHandler::reportError(request, ErrorHandler::InstructionExecutionStartError); + request.skipBytes(ECSS_TC_REQUEST_STRING_SIZE); + } else { + // Get the TC packet request + uint8_t requestData[ECSS_TC_REQUEST_STRING_SIZE] = {0}; + request.readString(requestData, ECSS_TC_REQUEST_STRING_SIZE); + Message receivedTCPacket = msgParser.parseRequestTC(requestData); + ScheduledActivity newActivity; // Create the new activity + + // Assign the attributes to the newly created activity + newActivity.request = receivedTCPacket; + newActivity.requestReleaseTime = releaseTime; + + // todo: When implemented save the source ID + newActivity.requestID.applicationID = request.applicationId; + newActivity.requestID.sequenceCount = request.packetSequenceCount; + + scheduledActivities.push_back(newActivity); // Insert the new activities + } + } + sortActivitiesReleaseTime(scheduledActivities); // Sort activities by their release time +} + +void TimeBasedSchedulingService::timeShiftAllActivities(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 15); + + uint32_t current_time = TimeGetter::getSeconds(); // Get the current system time + + // Find the earliest release time. It will be the first element of the iterator pair + const auto releaseTimes = etl::minmax_element(scheduledActivities.begin(), + scheduledActivities.end(), + [](ScheduledActivity const &leftSide, + ScheduledActivity const & + rightSide) { + return leftSide.requestReleaseTime < + rightSide.requestReleaseTime; + }); + // todo: Define what the time format is going to be + int32_t relativeOffset = request.readSint32(); // Get the relative offset + if ((releaseTimes.first->requestReleaseTime + relativeOffset) < + (current_time + ECSS_TIME_MARGIN_FOR_ACTIVATION)) { + // Report the error + ErrorHandler::reportError(request, ErrorHandler::SubServiceExecutionStartError); + } else { + for (auto &activity : scheduledActivities) { + activity.requestReleaseTime += relativeOffset; // Time shift each activity + } + } +} + +void TimeBasedSchedulingService::timeShiftActivitiesByID(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 7); + + uint32_t current_time = TimeGetter::getSeconds(); // Get the current system time + + int32_t relativeOffset = request.readSint32(); // Get the offset first + uint16_t iterationCount = request.readUint16(); // Get the iteration count, (N) + while (iterationCount--) { + // Parse the request ID + RequestID receivedRequestID; // Save the received request ID + receivedRequestID.sourceID = request.readUint8(); // Get the source ID + receivedRequestID.applicationID = request.readUint16(); // Get the application ID + receivedRequestID.sequenceCount = request.readUint16(); // Get the sequence count + + // Try to find the activity with the requested request ID + auto requestIDMatch = etl::find_if_not(scheduledActivities.begin(), + scheduledActivities.end(), + [&receivedRequestID] + (ScheduledActivity const ¤tElement) { + return receivedRequestID != + currentElement.requestID; + }); + + if (requestIDMatch != scheduledActivities.end()) { + // If the relative offset does not meet the restrictions issue an error + if ((requestIDMatch->requestReleaseTime + relativeOffset) < + (current_time + ECSS_TIME_MARGIN_FOR_ACTIVATION)) { + ErrorHandler::reportError(request, ErrorHandler::InstructionExecutionStartError); + } else { + requestIDMatch->requestReleaseTime += relativeOffset; // Add the time offset + } + } else { + ErrorHandler::reportError(request, ErrorHandler::InstructionExecutionStartError); + } + } + sortActivitiesReleaseTime(scheduledActivities); // Sort activities by their release time +} + +void TimeBasedSchedulingService::deleteActivitiesByID(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 5); + + uint16_t iterationCount = request.readUint16(); // Get the iteration count, (N) + while (iterationCount--) { + // Parse the request ID + RequestID receivedRequestID; // Save the received request ID + receivedRequestID.sourceID = request.readUint8(); // Get the source ID + receivedRequestID.applicationID = request.readUint16(); // Get the application ID + receivedRequestID.sequenceCount = request.readUint16(); // Get the sequence count + + // Try to find the activity with the requested request ID + const auto requestIDMatch = etl::find_if_not(scheduledActivities.begin(), + scheduledActivities.end(), [&receivedRequestID] + (ScheduledActivity const ¤tElement) { + return receivedRequestID != currentElement + .requestID; + }); + + if (requestIDMatch != scheduledActivities.end()) { + scheduledActivities.erase(requestIDMatch); // Delete activity from the schedule + } else { + ErrorHandler::reportError(request, ErrorHandler::InstructionExecutionStartError); + } + } +} + +void TimeBasedSchedulingService::detailReportAllActivities(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 16); + + // Create the report message object of telemetry message subtype 10 for each activity + Message report = createTM(10); + report.appendUint16(static_cast<uint16_t >(scheduledActivities.size())); + + for (auto &activity : scheduledActivities) { + // todo: append sub-schedule and group ID if they are defined + + report.appendUint32(activity.requestReleaseTime); + report.appendString(msgParser.convertTCToStr(activity.request)); + } + storeMessage(report); // Save the report +} + +void TimeBasedSchedulingService::detailReportActivitiesByID(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 9); + + // Create the report message object of telemetry message subtype 10 for each activity + Message report = createTM(10); + etl::list<ScheduledActivity, ECSS_MAX_NUMBER_OF_TIME_SCHED_ACTIVITIES> matchedActivities; + + uint16_t iterationCount = request.readUint16(); // Get the iteration count, (N) + while (iterationCount--) { + // Parse the request ID + RequestID receivedRequestID; // Save the received request ID + receivedRequestID.sourceID = request.readUint8(); // Get the source ID + receivedRequestID.applicationID = request.readUint16(); // Get the application ID + receivedRequestID.sequenceCount = request.readUint16(); // Get the sequence count + + // Try to find the activity with the requested request ID + const auto requestIDMatch = etl::find_if_not(scheduledActivities.begin(), + scheduledActivities.end(), [&receivedRequestID] + (ScheduledActivity const ¤tElement) { + return receivedRequestID != currentElement + .requestID; + }); + + if (requestIDMatch != scheduledActivities.end()) { + matchedActivities.push_back(*requestIDMatch); // Save the matched activity + } else { + ErrorHandler::reportError(request, ErrorHandler::InstructionExecutionStartError); + } + } + + sortActivitiesReleaseTime(matchedActivities); // Sort activities by their release time + + // todo: append sub-schedule and group ID if they are defined + report.appendUint16(static_cast<uint16_t >(matchedActivities.size())); + for (auto &match : matchedActivities) { + report.appendUint32(match.requestReleaseTime); // todo: Replace with the time parser + report.appendString(msgParser.convertTCToStr(match.request)); + } + storeMessage(report); // Save the report +} + +void TimeBasedSchedulingService::summaryReportActivitiesByID(Message &request) { + + // Check if the correct packet is being processed + assert(request.serviceType == 11); + assert(request.messageType == 12); + + // Create the report message object of telemetry message subtype 13 for each activity + Message report = createTM(13); + etl::list<ScheduledActivity, ECSS_MAX_NUMBER_OF_TIME_SCHED_ACTIVITIES> matchedActivities; + + uint16_t iterationCount = request.readUint16(); // Get the iteration count, (N) + while (iterationCount--) { + // Parse the request ID + RequestID receivedRequestID; // Save the received request ID + receivedRequestID.sourceID = request.readUint8(); // Get the source ID + receivedRequestID.applicationID = request.readUint16(); // Get the application ID + receivedRequestID.sequenceCount = request.readUint16(); // Get the sequence count + + // Try to find the activity with the requested request ID + auto requestIDMatch = etl::find_if_not(scheduledActivities.begin(), + scheduledActivities.end(), [&receivedRequestID] + (ScheduledActivity const ¤tElement) { + return receivedRequestID != currentElement + .requestID; + }); + + if (requestIDMatch != scheduledActivities.end()) { + matchedActivities.push_back(*requestIDMatch); + } else { + ErrorHandler::reportError(request, ErrorHandler::InstructionExecutionStartError); + } + } + sortActivitiesReleaseTime(matchedActivities); // Sort activities by their release time + + // todo: append sub-schedule and group ID if they are defined + report.appendUint16(static_cast<uint16_t >(matchedActivities.size())); + for (auto &match : matchedActivities) { + // todo: append sub-schedule and group ID if they are defined + report.appendUint32(match.requestReleaseTime); + report.appendUint8(match.requestID.sourceID); + report.appendUint16(match.requestID.applicationID); + report.appendUint16(match.requestID.sequenceCount); + } + storeMessage(report); // Save the report +} diff --git a/src/main.cpp b/src/main.cpp index 5615e2641d4329f20a94335f149b62b05f9279df..4e5a4f4652fe4463d4d20f1586d15eb48c922ec0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,11 +7,14 @@ #include "Services/RequestVerificationService.hpp" #include "Services/MemoryManagementService.hpp" #include "Services/EventReportService.hpp" +#include "Services/FunctionManagementService.hpp" #include "Services/TimeManagementService.hpp" #include "Services/EventActionService.hpp" +#include "Services/LargePacketTransferService.hpp" +#include "Services/TimeBasedSchedulingService.hpp" +#include "ServicePool.hpp" #include "Message.hpp" #include "MessageParser.hpp" -#include "Services/MemoryManagementService.hpp" #include "Helpers/CRCHelper.hpp" #include "ErrorHandler.hpp" #include "etl/String.hpp" @@ -239,22 +242,24 @@ int main() { eventActionService.addEventActionDefinitions(eventActionDefinition); Message eventActionDefinition1(19, 1, Message::TC, 1); eventActionDefinition1.appendEnum16(0); - eventActionDefinition1.appendEnum16(2); + eventActionDefinition1.appendEnum16(3); TCdata = "hi1"; eventActionDefinition1.appendString(TCdata); eventActionService.addEventActionDefinitions(eventActionDefinition1); Message eventActionDefinition2(19, 1, Message::TC, 1); eventActionDefinition2.appendEnum16(0); - eventActionDefinition2.appendEnum16(3); + eventActionDefinition2.appendEnum16(4); TCdata = "hi2"; eventActionDefinition2.appendString(TCdata); eventActionService.addEventActionDefinitions(eventActionDefinition2); Message eventActionDefinition3(19, 5, Message::TC, 1); - eventActionDefinition3.appendUint16(2); + eventActionDefinition3.appendUint16(3); eventActionDefinition3.appendUint16(0); eventActionDefinition3.appendUint16(2); eventActionDefinition3.appendUint16(0); eventActionDefinition3.appendUint16(3); + eventActionDefinition3.appendUint16(0); + eventActionDefinition3.appendUint16(4); eventActionService.disableEventActionDefinitions(eventActionDefinition3); std::cout << "Status of position 0,1,2 should be 000:" << eventActionService @@ -269,15 +274,17 @@ int main() { eventActionDefinition5.appendUint16(0); eventActionDefinition5.appendUint16(3); eventActionService.enableEventActionDefinitions(eventActionDefinition5); - std::cout << "\nStatus of position 0,1,2 should be 111:" << eventActionService + std::cout << "\nStatus of position 0,1,2 should be 110:" << eventActionService .eventActionDefinitionArray[0].enabled << eventActionService .eventActionDefinitionArray[1].enabled << eventActionService.eventActionDefinitionArray[2].enabled; Message eventActionDefinition4(19, 2, Message::TC, 1); - eventActionDefinition4.appendUint16(1); + eventActionDefinition4.appendUint16(2); eventActionDefinition4.appendUint16(0); eventActionDefinition4.appendUint16(2); + eventActionDefinition4.appendUint16(0); + eventActionDefinition4.appendUint16(3); eventActionService.deleteEventActionDefinitions(eventActionDefinition4); std::cout << "\nPositions 0,1 empty should be 11:" << static_cast<uint16_t>(eventActionService @@ -287,8 +294,54 @@ int main() { Message eventActionDefinition6(19, 3, Message::TC, 1); eventActionService.deleteAllEventActionDefinitions(eventActionDefinition6); std::cout << "\nPositions 0,1 empty should be 1:" << static_cast<uint16_t>(eventActionService - .eventActionDefinitionArray[0].empty); + .eventActionDefinitionArray[0].empty); + + + // ST13 test + + LargePacketTransferService largePacketTransferService; + String<256> dataToTransfer = "12345678"; + largePacketTransferService.firstDownlinkPartReport(1, 1, dataToTransfer); + + + // ST[11] test + TimeBasedSchedulingService timeBasedSchedulingService; + MessageParser msgParser; + auto currentTime = static_cast<uint32_t >(time(nullptr)); // Get the current system time + std::cout << "\n\nST[11] service is running"; + std::cout << "\nCurrent time in seconds (UNIX epoch): " << currentTime << std::endl; + + Message receivedMsg = Message(11, 1, Message::TC, 1); + Message testMessage1(6, 5, Message::TC, 1), testMessage2(4, 5, Message::TC, 1); + testMessage1.appendUint16(4253); // Append dummy data + testMessage2.appendUint16(45667); // Append dummy data + + timeBasedSchedulingService.enableScheduleExecution(receivedMsg); // Enable the schedule + + // Insert activities in the schedule + receivedMsg = Message(11, 4, Message::TC, 1); + receivedMsg.appendUint16(2); // Total number of requests + + receivedMsg.appendUint32(currentTime + 1556435); + receivedMsg.appendString(msgParser.convertTCToStr(testMessage1)); + + receivedMsg.appendUint32(currentTime + 1957232); + receivedMsg.appendString(msgParser.convertTCToStr(testMessage2)); + timeBasedSchedulingService.insertActivities(receivedMsg); + + // Time shift activities + receivedMsg = Message(11, 15, Message::TC, 1); + receivedMsg.appendSint32(-6789); + timeBasedSchedulingService.timeShiftAllActivities(receivedMsg); + std::cout << "Activities should be time shifted by: " << -6789 << " seconds." << std::endl; + + // Report the activities + receivedMsg = Message(11, 16, Message::TC, 1); + timeBasedSchedulingService.detailReportAllActivities(receivedMsg); + // Report the activities by ID + receivedMsg = Message(11, 12, Message::TC, 1); + timeBasedSchedulingService.summaryReportActivitiesByID(receivedMsg); return 0; } diff --git a/test/Services/FunctionManagementService.cpp b/test/Services/FunctionManagementService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1cd99bf509a995fd08ce849965d0cd6d3f28a83c --- /dev/null +++ b/test/Services/FunctionManagementService.cpp @@ -0,0 +1,52 @@ +#include "catch2/catch.hpp" +#include "Services/FunctionManagementService.hpp" +#include "ServicePool.hpp" +#include "ServiceTests.hpp" +#include <iostream> + +FunctionManagementService & fms = Services.functionManagement; + +void test(String<MAX_ARG_LENGTH> a) { + std::cout << a.c_str() << std::endl; +} + +TEST_CASE("ST[08] - Call Tests") { + + SECTION("Malformed name") { + ServiceTests::reset(); + fms.include(String<FUNC_NAME_LENGTH>("test"), &test); + Message msg(8, 1, Message::TC, 1); + msg.appendString(String<FUNC_NAME_LENGTH>("t3st")); + fms.call(msg); + CHECK(ServiceTests::get(0).messageType == 4); + CHECK(ServiceTests::get(0).serviceType == 1); + } + + SECTION("Too long message") { + ServiceTests::reset(); + fms.include(String<FUNC_NAME_LENGTH>("test"), &test); + Message msg(8, 1, Message::TC, 1); + msg.appendString(String<FUNC_NAME_LENGTH>("test")); + msg.appendString(String<65> + ("eqrhjweghjhwqgthjkrghthjkdsfhgsdfhjsdjsfdhgkjdfsghfjdgkdfsgdfgsgd")); + fms.call(msg); + CHECK(ServiceTests::get(0).messageType == 4); + CHECK(ServiceTests::get(0).serviceType == 1); + } +} + + +TEST_CASE("ST[08] - Insert Tests") { + + SECTION("Insertion to full pointer map") { + // make sure the pointer map is full to the brim + ServiceTests::reset(); + std::string name = "test"; // FOR TESTING ONLY! + + for (int i = 0; i < FUNC_MAP_SIZE + 1; i++) { + name += std::to_string(i); // different names to fill up the map + fms.include(String<FUNC_NAME_LENGTH>(name.c_str()), &test); + } + CHECK(ServiceTests::thrownError(ErrorHandler::InternalErrorType::FunctionMapFull)); + } +} diff --git a/test/Services/LargePacketTransferService.cpp b/test/Services/LargePacketTransferService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e7109ee763d620047241ac94cc0ab19a6aa73662 --- /dev/null +++ b/test/Services/LargePacketTransferService.cpp @@ -0,0 +1,71 @@ +#include <catch2/catch.hpp> +#include <Services/LargePacketTransferService.hpp> +#include <Message.hpp> +#include "ServiceTests.hpp" +#include <cstring> +#include <etl/String.hpp> + +LargePacketTransferService &lPT = Services.largePacketTransferService; + +TEST_CASE("First Downlink Part Report TM[13,1]", "[service][st13]") { + String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> string = String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> + ("12345678"); + lPT.firstDownlinkPartReport(1, 1, string); + REQUIRE(ServiceTests::hasOneMessage()); + Message report = ServiceTests::get(0); + CHECK(report.messageType == 1); + CHECK(report.serviceType == 13); + CHECK(report.readUint16() == 1); + CHECK(report.readUint16() == 1); + uint8_t string2[ECSS_MAX_FIXED_OCTET_STRING_SIZE]; + printf("%d", report.readOctetString(string2)); + auto a = String<ECSS_MAX_FIXED_OCTET_STRING_SIZE>(string2, 8); + CHECK(string.compare(a) == 0); +} + +TEST_CASE("Intermediate Downlink Part Report TM[13,2]", "[service][st13]") { + String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> string = String<ECSS_MAX_FIXED_OCTET_STRING_SIZE>( + "12345678"); + lPT.intermediateDownlinkPartReport(1, 1, string); + REQUIRE(ServiceTests::hasOneMessage()); + Message report = ServiceTests::get(0); + CHECK(report.messageType == 2); + CHECK(report.serviceType == 13); + CHECK(report.readUint16() == 1); + CHECK(report.readUint16() == 1); + uint8_t string2[ECSS_MAX_FIXED_OCTET_STRING_SIZE]; + report.readOctetString(string2); + auto a = String<ECSS_MAX_FIXED_OCTET_STRING_SIZE>(string2, 8); + CHECK(string.compare(a) == 0); +} + +TEST_CASE("Last Downlink Part Report TM[13,3]", "[service][st13]") { + String<ECSS_MAX_FIXED_OCTET_STRING_SIZE> string = String<ECSS_MAX_FIXED_OCTET_STRING_SIZE>( + "12345678"); + lPT.lastDownlinkPartReport(1, 1, string); + REQUIRE(ServiceTests::hasOneMessage()); + Message report = ServiceTests::get(0); + CHECK(report.messageType == 3); + CHECK(report.serviceType == 13); + CHECK(report.readUint16() == 1); + CHECK(report.readUint16() == 1); + uint8_t string2[ECSS_MAX_FIXED_OCTET_STRING_SIZE]; + report.readOctetString(string2); + auto a = String<ECSS_MAX_FIXED_OCTET_STRING_SIZE>(string2, 8); + CHECK(string.compare(a) == 0); +} + +TEST_CASE("First Uplink Part TC[13,9]", "[service][st13]") { + String<256> string = "12345678"; + CHECK(string.compare(lPT.firstUplinkPart(string)) == 0); +} + +TEST_CASE("Intermediate Uplink Part TC[13,10]", "[service][st13]") { + String<256> string = "12345678"; + CHECK(string.compare(lPT.intermediateUplinkPart(string)) == 0); +} + +TEST_CASE("Last Uplink Part TC[13,11]", "[service][st13]") { + String<256> string = "12345678"; + CHECK(string.compare(lPT.lastUplinkPart(string)) == 0); +} diff --git a/test/Services/TimeBasedSchedulingService.cpp b/test/Services/TimeBasedSchedulingService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfa878325c513b2a28dd5f1c840cd5d414a7c7ba --- /dev/null +++ b/test/Services/TimeBasedSchedulingService.cpp @@ -0,0 +1,439 @@ +#include <catch2/catch.hpp> +#include "ServiceTests.hpp" +#include <Services/TimeBasedSchedulingService.hpp> + +#include <ctime> +#include <vector> + +/* + * A namespace defined explicitly for the purposes of testing. This namespace contains a + * structure, which has been declared as a friend in the TimeBasedSchedulingService class, so + * that it can access the private members required for testing validation. + */ +namespace unit_test { + struct Tester { + static bool executionFunctionStatus(TimeBasedSchedulingService &tmService) { + return tmService.executionFunctionStatus; + } + + /* + * Read the private member scheduled activities and since it is a list and it can't be + * accessed, get each element and save it to a vector. + */ + static auto scheduledActivities(TimeBasedSchedulingService &tmService) { + std::vector<TimeBasedSchedulingService::ScheduledActivity*>listElements; + + for (auto &element : tmService.scheduledActivities) { + listElements.push_back(&element); + } + return listElements; // Return the list elements + } + }; +} + +Message testMessage1, testMessage2, testMessage3, testMessage4; +MessageParser msgParser; +auto currentTime = static_cast<uint32_t >(time(nullptr)); // Get the current system time +bool messagesPopulated = false; // Indicate whether the test messages are initialized + +// Run this function to set the service up before moving on with further testing +auto activityInsertion(TimeBasedSchedulingService &timeService) { + if (not messagesPopulated) { + // Initialize the test messages + testMessage1.serviceType = 6; + testMessage1.messageType = 5; + testMessage1.packetType = Message::TC; + testMessage1.applicationId = 8; // todo: Remove the dummy application ID + testMessage1.appendUint16(4253); // Append dummy data + + testMessage2.serviceType = 4; + testMessage2.messageType = 5; + testMessage2.packetType = Message::TC; + testMessage2.applicationId = 4; // todo: Remove the dummy application ID + testMessage2.appendUint16(45667); // Append dummy data + + testMessage3.serviceType = 3; + testMessage3.messageType = 2; + testMessage3.packetType = Message::TC; + testMessage3.appendUint16(456); // Append dummy data + + testMessage4.serviceType = 12; + testMessage4.messageType = 23; + testMessage4.packetType = Message::TC; + testMessage4.appendUint16(934); // Append dummy data + + messagesPopulated = true; // Indicate initialized test messages + } + + Message receivedMessage(11, 4, Message::TC, 1); + receivedMessage.appendUint16(4); // Total number of requests + + // Test activity 1 + receivedMessage.appendUint32(currentTime + 1556435); + receivedMessage.appendString(msgParser.convertTCToStr(testMessage1)); + + // Test activity 2 + receivedMessage.appendUint32(currentTime + 1957232); + receivedMessage.appendString(msgParser.convertTCToStr(testMessage2)); + + // Test activity 3 + receivedMessage.appendUint32(currentTime + 1726435); + receivedMessage.appendString(msgParser.convertTCToStr(testMessage3)); + + // Test activity 4 + receivedMessage.appendUint32(currentTime + 17248435); + receivedMessage.appendString(msgParser.convertTCToStr(testMessage4)); + + // Insert activities in the schedule. They have to be inserted sorted + timeService.insertActivities(receivedMessage); + + return unit_test::Tester::scheduledActivities(timeService); // Return the activities vector +} + + +TEST_CASE("TC[11,1] Enable Schedule Execution", "[service][st11]") { + TimeBasedSchedulingService timeService; + Message receivedMessage(11, 1, Message::TC, 1); + + timeService.enableScheduleExecution(receivedMessage); + CHECK(unit_test::Tester::executionFunctionStatus(timeService)); +} + +TEST_CASE("TC[11,2] Disable Schedule Execution", "[service][st11]") { + Message receivedMessage(11, 2, Message::TC, 1); + TimeBasedSchedulingService timeService; + + timeService.disableScheduleExecution(receivedMessage); + CHECK(not unit_test::Tester::executionFunctionStatus(timeService)); +} + +TEST_CASE("TC[11,4] Activity Insertion", "[service][st11]") { + TimeBasedSchedulingService timeService; + auto scheduledActivities = activityInsertion(timeService); + + REQUIRE(scheduledActivities.size() == 4); + REQUIRE(scheduledActivities.at(0)->requestReleaseTime == currentTime + 1556435); + REQUIRE(scheduledActivities.at(1)->requestReleaseTime == currentTime + 1726435); + REQUIRE(scheduledActivities.at(2)->requestReleaseTime == currentTime + 1957232); + REQUIRE(scheduledActivities.at(3)->requestReleaseTime == currentTime + 17248435); + REQUIRE(scheduledActivities.at(0)->request == testMessage1); + REQUIRE(scheduledActivities.at(1)->request == testMessage3); + REQUIRE(scheduledActivities.at(2)->request == testMessage2); + REQUIRE(scheduledActivities.at(3)->request == testMessage4); + + SECTION("Error throw test") { + Message receivedMessage(11, 4, Message::TC, 1); + receivedMessage.appendUint16(1); // Total number of requests + + receivedMessage.appendUint32(currentTime - 15564350); + timeService.insertActivities(receivedMessage); + + REQUIRE(ServiceTests::thrownError(ErrorHandler::InstructionExecutionStartError)); + } +} + +TEST_CASE("TC[11,15] Time shift all scheduled activities", "[service][st11]") { + TimeBasedSchedulingService timeService; + Message receivedMessage(11, 15, Message::TC, 1); + + auto scheduledActivities = activityInsertion(timeService); + const int32_t timeShift = 6789; + + SECTION("Positive Shift") { + receivedMessage.appendSint32(-timeShift); + + CHECK(scheduledActivities.size() == 4); + timeService.timeShiftAllActivities(receivedMessage); + + REQUIRE(scheduledActivities.at(0)->requestReleaseTime == currentTime + 1556435 - timeShift); + REQUIRE(scheduledActivities.at(1)->requestReleaseTime == currentTime + 1726435 - timeShift); + REQUIRE(scheduledActivities.at(2)->requestReleaseTime == currentTime + 1957232 - timeShift); + REQUIRE( + scheduledActivities.at(3)->requestReleaseTime == currentTime + 17248435 - timeShift); + } + + SECTION("Negative Shift") { + receivedMessage.appendSint32(timeShift); + + CHECK(scheduledActivities.size() == 4); + timeService.timeShiftAllActivities(receivedMessage); + + REQUIRE(scheduledActivities.at(0)->requestReleaseTime == currentTime + 1556435 + timeShift); + REQUIRE(scheduledActivities.at(1)->requestReleaseTime == currentTime + 1726435 + timeShift); + REQUIRE(scheduledActivities.at(2)->requestReleaseTime == currentTime + 1957232 + timeShift); + REQUIRE( + scheduledActivities.at(3)->requestReleaseTime == currentTime + 17248435 + timeShift); + } + + SECTION("Error throwing") { + receivedMessage.appendSint32(-6789000); // Provide a huge time shift to cause an error + + CHECK(scheduledActivities.size() == 4); + timeService.timeShiftAllActivities(receivedMessage); + + REQUIRE(ServiceTests::thrownError(ErrorHandler::SubServiceExecutionStartError)); + } +} + +TEST_CASE("TC[11,7] Time shift activities by ID", "[service][st11]") { + TimeBasedSchedulingService timeService; + Message receivedMessage(11, 7, Message::TC, 1); + + auto scheduledActivities = activityInsertion(timeService); + scheduledActivities.at(2)->requestID.applicationID = 4; // Append a dummy application ID + CHECK(scheduledActivities.size() == 4); + + const int32_t timeShift = 67890000; // Relative time-shift value + + SECTION("Positive Shift") { + receivedMessage.appendSint32(timeShift); // Time-shift value + receivedMessage.appendUint16(1); // Just one instruction to time-shift an activity + receivedMessage.appendUint8(0); // Source ID is not implemented + receivedMessage.appendUint16(testMessage2.applicationId); // todo: Remove the dummy app ID + receivedMessage.appendUint16(0); // todo: Remove the dummy sequence count + + timeService.timeShiftActivitiesByID(receivedMessage); + scheduledActivities = unit_test::Tester::scheduledActivities(timeService); + + // Make sure the new value is inserted sorted + REQUIRE(scheduledActivities.at(3)->requestReleaseTime == currentTime + 1957232 + timeShift); + REQUIRE(scheduledActivities.at(3)->request == testMessage2); + } + + SECTION("Negative Shift") { + receivedMessage.appendSint32(-250000); // Time-shift value + receivedMessage.appendUint16(1); // Just one instruction to time-shift an activity + receivedMessage.appendUint8(0); // Source ID is not implemented + receivedMessage.appendUint16(testMessage2.applicationId); // todo: Remove the dummy app ID + receivedMessage.appendUint16(0); // todo: Remove the dummy sequence count + + timeService.timeShiftActivitiesByID(receivedMessage); + scheduledActivities = unit_test::Tester::scheduledActivities(timeService); + + // Output should be sorted + REQUIRE(scheduledActivities.at(1)->requestReleaseTime == currentTime + 1957232 - 250000); + REQUIRE(scheduledActivities.at(1)->request == testMessage2); + } + + SECTION("Error throw on wrong request ID") { + receivedMessage.appendSint32(-250000); // Time-shift value + receivedMessage.appendUint16(1); // Just one instruction to time-shift an activity + receivedMessage.appendUint8(0); // Dummy source ID + receivedMessage.appendUint16(80); // Dummy application ID to throw an error + receivedMessage.appendUint16(0); // Dummy sequence count + + timeService.timeShiftActivitiesByID(receivedMessage); + REQUIRE(ServiceTests::thrownError(ErrorHandler::InstructionExecutionStartError)); + } + + SECTION("Error throw on wrong time offset") { + receivedMessage.appendSint32(-6789000); // Time-shift value + receivedMessage.appendUint16(1); // Just one instruction to time-shift an activity + receivedMessage.appendUint8(0); // Source ID is not implemented + receivedMessage.appendUint16(testMessage2.applicationId); // todo: Remove the dummy app ID + receivedMessage.appendUint16(0); // todo: Remove the dummy sequence count + + timeService.timeShiftActivitiesByID(receivedMessage); + REQUIRE(ServiceTests::thrownError(ErrorHandler::InstructionExecutionStartError)); + } +} + +TEST_CASE("TC[11,9] Detail report scheduled activities by ID", "[service][st11]") { + TimeBasedSchedulingService timeService; + Message receivedMessage(11, 9, Message::TC, 1); + + auto scheduledActivities = activityInsertion(timeService); + + SECTION("Detailed activity report") { + // Verify that everything is in place + CHECK(scheduledActivities.size() == 4); + scheduledActivities.at(0)->requestID.applicationID = 8; // Append a dummy application ID + scheduledActivities.at(2)->requestID.applicationID = 4; // Append a dummy application ID + + receivedMessage.appendUint16(2); // Two instructions in the request + receivedMessage.appendUint8(0); // Source ID is not implemented + receivedMessage.appendUint16(testMessage2.applicationId); // todo: Remove the dummy app ID + receivedMessage.appendUint16(0); // todo: Remove the dummy sequence count + + receivedMessage.appendUint8(0); // Source ID is not implemented + receivedMessage.appendUint16(testMessage1.applicationId); // todo: Remove the dummy app ID + receivedMessage.appendUint16(0); // todo: Remove the dummy sequence count + + timeService.detailReportActivitiesByID(receivedMessage); + REQUIRE(ServiceTests::hasOneMessage()); + + Message response = ServiceTests::get(0); + CHECK(response.serviceType == 11); + CHECK(response.messageType == 10); + + uint16_t iterationCount = response.readUint16(); + CHECK(iterationCount == 2); + for (uint16_t i = 0; i < iterationCount; i++) { + uint32_t receivedReleaseTime = response.readUint32(); + + Message receivedTCPacket; + uint8_t receivedDataStr[ECSS_TC_REQUEST_STRING_SIZE]; + response.readString(receivedDataStr, ECSS_TC_REQUEST_STRING_SIZE); + receivedTCPacket = msgParser.parseRequestTC(receivedDataStr); + + if (i == 0) { + REQUIRE(receivedReleaseTime == scheduledActivities.at(0)->requestReleaseTime); + REQUIRE(receivedTCPacket == scheduledActivities.at(0)->request); + } else { + REQUIRE(receivedReleaseTime == scheduledActivities.at(2)->requestReleaseTime); + REQUIRE(receivedTCPacket == scheduledActivities.at(2)->request); + } + } + } + + SECTION("Error throw on wrong request ID") { + receivedMessage.appendUint16(1); // Just one instruction to time-shift an activity + receivedMessage.appendUint8(0); // Dummy source ID + receivedMessage.appendUint16(80); // Dummy application ID to throw an error + receivedMessage.appendUint16(0); // Dummy sequence count + + timeService.detailReportActivitiesByID(receivedMessage); + REQUIRE(ServiceTests::thrownError(ErrorHandler::InstructionExecutionStartError)); + } +} + +TEST_CASE("TC[11,12] Summary report scheduled activities by ID", "[service][st11]") { + TimeBasedSchedulingService timeService; + Message receivedMessage(11, 12, Message::TC, 1); + + auto scheduledActivities = activityInsertion(timeService); + + SECTION("Summary report") { + // Verify that everything is in place + CHECK(scheduledActivities.size() == 4); + scheduledActivities.at(0)->requestID.applicationID = 8; // Append a dummy application ID + scheduledActivities.at(2)->requestID.applicationID = 4; // Append a dummy application ID + + receivedMessage.appendUint16(2); // Two instructions in the request + receivedMessage.appendUint8(0); // Source ID is not implemented + receivedMessage.appendUint16(testMessage2.applicationId); // todo: Remove the dummy app ID + receivedMessage.appendUint16(0); // todo: Remove the dummy sequence count + + receivedMessage.appendUint8(0); // Source ID is not implemented + receivedMessage.appendUint16(testMessage1.applicationId); // todo: Remove the dummy app ID + receivedMessage.appendUint16(0); // todo: Remove the dummy sequence count + + timeService.summaryReportActivitiesByID(receivedMessage); + REQUIRE(ServiceTests::hasOneMessage()); + + Message response = ServiceTests::get(0); + CHECK(response.serviceType == 11); + CHECK(response.messageType == 13); + + uint16_t iterationCount = response.readUint16(); + for (uint16_t i = 0; i < iterationCount; i++) { + uint32_t receivedReleaseTime = response.readUint32(); + uint8_t receivedSourceID = response.readUint8(); + uint16_t receivedApplicationID = response.readUint16(); + uint16_t receivedSequenceCount = response.readUint16(); + + if (i == 0) { + REQUIRE(receivedReleaseTime == scheduledActivities.at(0)->requestReleaseTime); + REQUIRE(receivedSourceID == scheduledActivities.at(0)->requestID.sourceID); + REQUIRE( + receivedApplicationID == scheduledActivities.at(0)->requestID.applicationID); + REQUIRE( + receivedSequenceCount == scheduledActivities.at(0)->requestID.sequenceCount); + } else { + REQUIRE(receivedReleaseTime == scheduledActivities.at(2)->requestReleaseTime); + REQUIRE(receivedSourceID == scheduledActivities.at(2)->requestID.sourceID); + REQUIRE( + receivedApplicationID == scheduledActivities.at(2)->requestID.applicationID); + REQUIRE( + receivedSequenceCount == scheduledActivities.at(2)->requestID.sequenceCount); + } + } + } + + SECTION("Error throw on wrong request ID") { + receivedMessage.appendUint16(1); // Just one instruction to time-shift an activity + receivedMessage.appendUint8(0); // Dummy source ID + receivedMessage.appendUint16(80); // Dummy application ID to throw an error + receivedMessage.appendUint16(0); // Dummy sequence count + + timeService.summaryReportActivitiesByID(receivedMessage); + REQUIRE(ServiceTests::thrownError(ErrorHandler::InstructionExecutionStartError)); + } +} + +TEST_CASE("TC[11,16] Detail report all scheduled activities", "[service][st11]") { + TimeBasedSchedulingService timeService; + auto scheduledActivities = activityInsertion(timeService); + + Message receivedMessage(11, 16, Message::TC, 1); + timeService.detailReportAllActivities(receivedMessage); + REQUIRE(ServiceTests::hasOneMessage()); + + Message response = ServiceTests::get(0); + CHECK(response.serviceType == 11); + CHECK(response.messageType == 10); + + uint16_t iterationCount = response.readUint16(); + REQUIRE(iterationCount == scheduledActivities.size()); + + for (uint16_t i = 0; i < iterationCount; i++) { + uint32_t receivedReleaseTime = response.readUint32(); + + Message receivedTCPacket; + uint8_t receivedDataStr[ECSS_TC_REQUEST_STRING_SIZE]; + response.readString(receivedDataStr, ECSS_TC_REQUEST_STRING_SIZE); + receivedTCPacket = msgParser.parseRequestTC(receivedDataStr); + + REQUIRE(receivedReleaseTime == scheduledActivities.at(i)->requestReleaseTime); + REQUIRE(scheduledActivities.at(i)->request == receivedTCPacket); + } +} + +TEST_CASE("TC[11,5] Activity deletion by ID", "[service][st11]") { + TimeBasedSchedulingService timeService; + Message receivedMessage(11, 5, Message::TC, 1); + + auto scheduledActivities = activityInsertion(timeService); + + SECTION("Activity deletion") { + // Verify that everything is in place + CHECK(scheduledActivities.size() == 4); + scheduledActivities.at(2)->requestID.applicationID = 4; // Append a dummy application ID + + receivedMessage.appendUint16(1); // Just one instruction to delete an activity + receivedMessage.appendUint8(0); // Source ID is not implemented + receivedMessage.appendUint16(testMessage2.applicationId); // todo: Remove the dummy app ID + receivedMessage.appendUint16(0); // todo: Remove the dummy sequence count + + CHECK(scheduledActivities.size() == 4); + timeService.deleteActivitiesByID(receivedMessage); + scheduledActivities = unit_test::Tester::scheduledActivities(timeService); + + REQUIRE(scheduledActivities.size() == 3); + REQUIRE(scheduledActivities.at(2)->requestReleaseTime == currentTime + 17248435); + REQUIRE(scheduledActivities.at(2)->request == testMessage4); + } + + SECTION("Error throw on wrong request ID") { + receivedMessage.appendUint16(1); // Just one instruction to time-shift an activity + receivedMessage.appendUint8(0); // Dummy source ID + receivedMessage.appendUint16(80); // Dummy application ID to throw an error + receivedMessage.appendUint16(0); // Dummy sequence count + + timeService.deleteActivitiesByID(receivedMessage); + REQUIRE(ServiceTests::thrownError(ErrorHandler::InstructionExecutionStartError)); + } +} + +TEST_CASE("TC[11,3] Reset schedule", "[service][st11]") { + TimeBasedSchedulingService timeService; + auto scheduledActivities = activityInsertion(timeService); + + Message receivedMessage(11, 3, Message::TC, 1); + + timeService.resetSchedule(receivedMessage); + scheduledActivities = unit_test::Tester::scheduledActivities(timeService); // Get the new list + + REQUIRE(scheduledActivities.empty()); + REQUIRE(not unit_test::Tester::executionFunctionStatus(timeService)); +}