diff --git a/CMakeLists.txt b/CMakeLists.txt index a60c3aa629404e5c408e4a097762ca078bea7e81..0b046fbdf84bdb2de8dcf8f30a35096a515b4f73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,8 +28,9 @@ add_library(common OBJECT src/Services/RequestVerificationService.cpp src/Services/TestService.cpp src/Services/TimeManagementService.cpp + src/Services/TimeBasedSchedulingService.cpp + src/Services/EventActionService.cpp src/Services/FunctionManagementService.cpp - src/Services/EventActionService.cpp ) # Specify the .cpp files for the executables diff --git a/inc/ECSS_Definitions.hpp b/inc/ECSS_Definitions.hpp index 1f000979a32969565a7cdee0d7e91f63779da207..de8033326c5e80391f4729fd889124f331bcc919 100644 --- a/inc/ECSS_Definitions.hpp +++ b/inc/ECSS_Definitions.hpp @@ -9,9 +9,41 @@ #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 75b116b5bf3df82c51287de78441136fc2e453a2..4f50837588ce77417bc2818c2093b675a9bfd267 100644 --- a/inc/ErrorHandler.hpp +++ b/inc/ErrorHandler.hpp @@ -110,6 +110,8 @@ public: */ enum ExecutionStartErrorType { UnknownExecutionStartError = 0, + SubServiceExecutionStartError = 1, + InstructionExecutionStartError = 2, }; /** 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/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/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/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/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/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/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 2fcf832da10d29251cc5365c865f8ef78a90ac9c..6067a82a5459d2c2137f0b8702484c7ece768791 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,9 +10,9 @@ #include "Services/FunctionManagementService.hpp" #include "Services/TimeManagementService.hpp" #include "Services/EventActionService.hpp" +#include "Services/TimeBasedSchedulingService.hpp" #include "Message.hpp" #include "MessageParser.hpp" -#include "Services/MemoryManagementService.hpp" #include "Helpers/CRCHelper.hpp" #include "ErrorHandler.hpp" #include "etl/String.hpp" @@ -291,5 +291,44 @@ int main() { .eventActionDefinitionArray[0].empty); + // 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/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)); +}