diff --git a/CMakeLists.txt b/CMakeLists.txt index 37cdc1f4354b5c0253f91ad8887d46dc9b871910..037f051530f4e6a8d23967a464d3a5b11667110a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,7 @@ add_library(common OBJECT src/MessageParser.cpp src/ServicePool.cpp src/Helpers/CRCHelper.cpp - src/Helpers/TimeAndDate.cpp - src/Helpers/TimeHelper.cpp + src/Helpers/TimeFormats.cpp src/Services/EventReportService.cpp src/Services/MemoryManagementService.cpp src/Services/ParameterService.cpp diff --git a/inc/ErrorHandler.hpp b/inc/ErrorHandler.hpp index 2936e1f2ffb4d7c5f7f41e281cde943c9936969a..b05d465fedb11f28a8b2cd827586b7321fae94c3 100644 --- a/inc/ErrorHandler.hpp +++ b/inc/ErrorHandler.hpp @@ -85,6 +85,10 @@ public: * Attempt to access an invalid parameter in ST[03] */ NonExistentParameter = 13, + /** + * Invalid TimeStamp parameters at creation + */ + InvalidTimeStampInput = 14 }; /** diff --git a/inc/Helpers/TimeAndDate.hpp b/inc/Helpers/TimeAndDate.hpp deleted file mode 100644 index 03fb0833d3d542b81d48b0f9d31e707220a88fa7..0000000000000000000000000000000000000000 --- a/inc/Helpers/TimeAndDate.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef ECSS_SERVICES_TIMEANDDATE_HPP -#define ECSS_SERVICES_TIMEANDDATE_HPP - -#include <cstdint> -#include "macros.hpp" - -/** - * A class that represents the time and date. - * - * @note - * This class represents UTC (Coordinated Universal Time) date - */ -class TimeAndDate { -public: - uint16_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - - /** - * Assign the instances with the Unix epoch 1/1/1970 00:00:00 - */ - TimeAndDate(); - - /** - * @param year the year as it used in Gregorian calendar - * @param month the month as it used in Gregorian calendar - * @param day the day as it used in Gregorian calendar - * @param hour UTC hour in 24 format - * @param minute UTC minutes - * @param second UTC seconds - */ - TimeAndDate(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); - - /** - * Compare two timestamps. - * - * @param Date the date that will be compared with the pointer `this` - * @return true if the pointer `this` is smaller than \p Date - */ - bool operator<(const TimeAndDate& Date); - - /** - * Compare two timestamps. - * - * @param Date the date that will be compared with the pointer `this` - * @return true if the pointer `this` is greater than \p Date - */ - bool operator>(const TimeAndDate& Date); - - /** - * Compare two timestamps. - * - * @param Date the date that will be compared with the pointer `this` - * @return true if the pointer `this` is equal to \p Date - */ - bool operator==(const TimeAndDate& Date); - - /** - * Compare two timestamps. - * - * @param Date the date that will be compared with the pointer `this` - * @return true if the pointer `this` is smaller than or equal to \p Date - */ - bool operator<=(const TimeAndDate& Date); - - /** - * Compare two timestamps. - * - * @param Date the date that will be compared with the pointer `this` - * @return true if the pointer `this` is greater than or equal to \p Date - */ - bool operator>=(const TimeAndDate& Date); -}; - -#endif // ECSS_SERVICES_TIMEANDDATE_HPP diff --git a/inc/Helpers/TimeFormats.hpp b/inc/Helpers/TimeFormats.hpp new file mode 100644 index 0000000000000000000000000000000000000000..969c52da5ce3787801fb6a7cbf57f2591c98df25 --- /dev/null +++ b/inc/Helpers/TimeFormats.hpp @@ -0,0 +1,221 @@ +#include <cstdint> +#include <Message.hpp> +#include "macros.hpp" +#include "etl/String.hpp" + +// SEE CCSDS 301.0-B-4 + +//////////////// CONSTANTS //////////////////// +inline constexpr uint8_t SECONDS_PER_MINUTE = 60; +inline constexpr uint16_t SECONDS_PER_HOUR = 3600; +inline constexpr uint32_t SECONDS_PER_DAY = 86400; +static constexpr uint8_t DAYSOFMONTH[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +inline constexpr uint8_t ACUBESAT_CUC_SECONDS_COUNTER_BYTES = 2; // PER DDJF_TTC +inline constexpr uint8_t ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES = 2; // PER DDJF_TTC +// Unix Epoch includes leap seconds. +// Currently set at 01 Jan 2019 00h00m00s, TODO correct to set to 01 Jan. 2020 00h00m00s. +// May be broken, see Issue #121 on Gitlab. +inline constexpr uint32_t UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS = 1546300800; +// +inline constexpr uint16_t ACUBESAT_EPOCH_YEAR = 2019; +inline constexpr uint8_t ACUBESAT_EPOCH_MONTH = 1; +inline constexpr uint8_t ACUBESAT_EPOCH_DAY = 1; + +inline constexpr uint8_t MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP = 9; + +static_assert(ACUBESAT_EPOCH_YEAR >= 2019); +static_assert(ACUBESAT_EPOCH_MONTH < 11 && ACUBESAT_EPOCH_MONTH >= 0); +static_assert(ACUBESAT_EPOCH_DAY < DAYSOFMONTH[ACUBESAT_EPOCH_MONTH]); +////////////////////////////////////////////// + +//////// HELPER CONSTEXPR //////// +// Build CUC header at compile time for timestamps that have a short (1 byte) header +template <int seconds_counter_bytes, int fractional_counter_bytes> +inline constexpr uint8_t build_short_CUC_header() { + static_assert(seconds_counter_bytes <= 4, "Use build_long_CUC_header instead"); + static_assert(fractional_counter_bytes <= 3, "Use build_long_CUC_header instead"); + + uint8_t header = 0; + + // P-Field extension is 0, CUC header is not extended + header += 0; + + // AcubeSAT is using custom TAI epoch at 01 Jan 2020 + header = header << 3; + header += 0b010; + + // Number of bytes in the basic time unit + header = header << 2; + header += seconds_counter_bytes - 1; + + // Number of bytes in the fractional unit + header = header << 2; + header += fractional_counter_bytes; + + return header; +} + +// Build CUC header at compile time for timestamps that have a long (2 byte) header +template <int seconds_counter_bytes, int fractional_counter_bytes> +inline constexpr uint16_t build_long_CUC_header() { + // cppcheck-suppress redundantCondition + static_assert(seconds_counter_bytes > 4 || fractional_counter_bytes > 3, "Use build_short_CUC_header instead"); + static_assert(seconds_counter_bytes <= 7, + "Number of bytes for seconds over maximum number of octets allowed by CCSDS"); + static_assert(fractional_counter_bytes <= 6, + "Number of bytes for seconds over maximum number of octets allowed by CCSDS"); + + uint16_t header = 0; + + uint8_t first_octet_number_of_seconds_bytes = std::min(4, seconds_counter_bytes); + uint8_t second_octet_number_of_seconds_bytes = seconds_counter_bytes - first_octet_number_of_seconds_bytes; + + uint8_t first_octet_number_of_fractional_bytes = std::min(3, fractional_counter_bytes); + uint8_t second_octet_number_of_fractional_bytes = fractional_counter_bytes - first_octet_number_of_fractional_bytes; + + // P-Field extension is 1, CUC header is extended + header += 1; + + // AcubeSAT is using custom TAI epoch at 01 Jan 2020 + header = header << 3; + header += 0b010; + + // // Number of bytes in the basic time unit + header = header << 2; + header += first_octet_number_of_seconds_bytes - 1; + + // Number of bytes in the fractional unit + header = header << 2; + header += first_octet_number_of_fractional_bytes; + + // P-Field extension is 1, CUC header was extended + header = header << 1; + header += 1; + + // Number of bytes in the extended basic time unit + header = header << 2; + header += second_octet_number_of_seconds_bytes; + + // Number of bytes in the extended fractional unit + header = header << 3; + header += second_octet_number_of_fractional_bytes; + + // Last 3 LSb are reserved for custom mission use + header = header << 2; + header += 0; + + return header; +} + +template <typename T, int seconds_counter_bytes, int fractional_counter_bytes> +inline constexpr T build_CUC_header() { + static_assert((seconds_counter_bytes + fractional_counter_bytes) <= 8, + "Complete arbitrary precision not yet supported"); // TODO: see Issue #106 on Gitlab + // cppcheck-suppress syntaxError + // cppcheck-suppress redundantCondition + if constexpr (seconds_counter_bytes <= 4 && + fractional_counter_bytes <= 3) // if constexpr not supported yet in cppcheck + return build_short_CUC_header<seconds_counter_bytes, fractional_counter_bytes>(); + else + return build_long_CUC_header<seconds_counter_bytes, fractional_counter_bytes>(); +} + +inline constexpr uint8_t build_AcubeSAT_CDS_header() { + uint8_t header = 0; + + // bit 0 is at 0 + header += 0; + header << 1; + + // timecode identification + header += 0b100; + header << 3; + + // AcubeSAT is using custom TAI epoch at 01 Jan 2020 + header += 1; + header << 1; + + // AcubeSAT is using 16 bits day count segment + header += 0; + header << 1; + + // AcubeSAT is using picosecond resolution + header += 0b10; + // header << 2; + + return header; +} +//////////////////////////////////////////////// + +////////// CONVENIENCE FUNCTIONS //////////// +bool is_leap_year(uint16_t year); + +////////// Transitory timestamps //////////// +// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX) +class AcubeSAT_CDS_timestamp { +public: + static constexpr uint8_t P_FIELD = build_AcubeSAT_CDS_header(); + uint16_t day; + uint16_t ms_of_day; + uint32_t submilliseconds; + + uint64_t to_CDS_timestamp(); + void from_CDS_timestamp(uint64_t); +}; + +// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX) +/** + * A class that represents UTC time and date, from UNIX EPOCH + * + * @note + * This class represents UTC (Coordinated Universal Time) date + */ +class UTC_Timestamp { +public: + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + + /** + * Assign the instances with the Unix epoch 1/1/1970 00:00:00 + */ + UTC_Timestamp(); + + /** + * @param year the year as it used in Gregorian calendar + * @param month the month as it used in Gregorian calendar + * @param day the day as it used in Gregorian calendar + * @param hour UTC hour in 24 format + * @param minute UTC minutes + * @param second UTC seconds + */ + UTC_Timestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); + + /** + * @param text_timestamp the timestamp to parse into a UTC date + */ + UTC_Timestamp(etl::string<32> text_timestamp); + + /** + * Compare two timestamps. + * + * @param Date the date that will be compared with the pointer `this` + * @return true if the condition is satisfied + */ + bool operator<(const UTC_Timestamp& Date); + bool operator>(const UTC_Timestamp& Date); + bool operator==(const UTC_Timestamp& Date); + bool operator<=(const UTC_Timestamp& Date); + bool operator>=(const UTC_Timestamp& Date); + + /** + * Pretty-print timestamp. + * + * @param Date the date that will be output + */ + friend std::ostream& operator<<(std::ostream& o, UTC_Timestamp const& Date); +}; diff --git a/inc/Helpers/TimeHelper.hpp b/inc/Helpers/TimeHelper.hpp deleted file mode 100644 index 42b5baf1204b01c58b3e964c5d413a6a973ea26f..0000000000000000000000000000000000000000 --- a/inc/Helpers/TimeHelper.hpp +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef ECSS_SERVICES_TIMEHELPER_HPP -#define ECSS_SERVICES_TIMEHELPER_HPP - -#include <cstdint> -#include <Message.hpp> -#include "TimeAndDate.hpp" - -inline const uint8_t SecondsPerMinute = 60U; -inline const uint16_t SecondsPerHour = 3600U; -inline const uint32_t SecondsPerDay = 86400U; - -/** - * @todo If we use CUC time format then we should keep leap seconds up to date. Leap seconds are added in undefined - * periods of time, so we should find a way to adjust to these changes either in runtime using GPS or sending a new - * compiled code (changing the defined leap seconds) from the ground segment - */ -inline const uint8_t LeapSeconds = 27; - - -/** - * This class formats the spacecraft time and cooperates closely with the ST[09] time management. - * - * The ECSS standard supports two time formats: the CUC and CSD that are described in CCSDS - * 301.0-B-4 standard. - * - * The CDS is UTC-based (UTC: Coordinated Universal Time). It consists of two main fields: the - * time code preamble field (P-field) and the time specification field (T-field). The P-Field is the metadata for the - * T-Field. The T-Field is consisted of two segments: 1) the `DAY` and the 2) `ms of day` segments. The P-field won't - * be included in the code, because as the ECSS standards claims, it can be just implicitly declared. - * - * The CUC is TAI-based (TAI: International Atomic Time). It consists of two main fields: the time code preamble field - * (P-field) and the time specification field(T-field). The T-Field contains the value of the time unit and the designer - * decides what the time unit will be, so this is a subject for discussion. The recommended time unit from the - * standard is the second and it is probably the best solution for accuracy. - * - * @note - * The defined epoch for both time formats is 1 January 1958 00:00:00 - * - * Since CDS format is UTC-based, the leap second correction must be made. The leap seconds that - * have been occurred between timestamps should be considered if a critical time-difference is - * needed - * - */ -class TimeHelper { -public: - static constexpr uint8_t DaysOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - - TimeHelper() = default; - - /** - * @param year The year that will be examined if it is a leap year (366 days) - * @return if the \p year is a leap year returns true and if it isn't returns false - */ - static bool IsLeapYear(uint16_t year); - - /** - * Convert UTC date to elapsed seconds since Unix epoch (1/1/1970 00:00:00). - * - * This is a reimplemented `mktime()` of the `<ctime>` library in an embedded compatible way - * - * @note - * This function can convert UTC dates after 1 January 2019 to elapsed seconds since Unix epoch - * - * @param TimeInfo the time information/data from the RTC (UTC format) - * @return the elapsed seconds between a given UTC date (after the Unix epoch) and Unix epoch - * @todo check if we need to change the epoch to the recommended one from the standard, 1 - * January 1958 - */ - static uint32_t utcToSeconds(const TimeAndDate& TimeInfo); - - /** - * Convert elapsed seconds since Unix epoch to UTC date. - * - * This is a reimplemented `gmtime()` of the `<ctime>` library in an embedded compatible way - * - * @note - * This function can convert elapsed seconds since Unix epoch to UTC dates after 1 January 2019 - * - * @param seconds elapsed seconds since Unix epoch - * @return the UTC date based on the \p seconds - * @todo check if we need to change the epoch to the recommended one from the standard, 1 - * January 1958 - */ - static TimeAndDate secondsToUTC(uint32_t seconds); - - /** - * Generate the CDS time format (3.3 in CCSDS 301.0-B-4 standard). - * - * Converts a UTC date to CDS time format. - * - * @param TimeInfo is the data provided from RTC (UTC) - * @return TimeFormat the CDS time format. More specific, 48 bits are used for the T-field - * (16 for the `DAY` and 32 for the `ms of day`) - * @todo time security for critical time operations - * @todo declare the implicit P-field - */ - static uint64_t generateCDSTimeFormat(const struct TimeAndDate& TimeInfo); - - /** - * Parse the CDS time format (3.3 in CCSDS 301.0-B-4 standard) - * - * @param data time information provided from the ground segment. The length of the data is a - * fixed size of 48 bits - * @return the UTC date - */ - static TimeAndDate parseCDStimeFormat(const uint8_t* data); - - /** - * Generate the CUC time format (3.3 in CCSDS 301.0-B-4 standard). - * - * Converts a UTC date to CUC time format. - * - * @note - * The T-field is specified for the seconds passed from the defined epoch 1 January 1958. We use 4 octets(32 - * bits) for the time unit (seconds) because 32 bits for the seconds are enough to count 136 years! But if we use 24 - * bits for the seconds then it will count 0,5 years and this isn't enough. Remember we can use only integers - * numbers of octets for the time unit (second) - * - * The CUC time format doesn't include leap seconds, so we need to add them because we assume that - * the RTC will provide UTC format. - * - * @param TimeInfo is the data provided from RTC (UTC) - * @return TimeFormat the CUC time format. More specific, 32 bits are used for the T-field (seconds since 1/1/1958) - * @todo time security for critical time operations - * @todo declare the implicit P-field - */ - static uint32_t generateCUCTimeFormat(const struct TimeAndDate& TimeInfo); - - /** - * Parse the CUC time format (3.3 in CCSDS 301.0-B-4 standard) - * - * @param data time information provided from the ground segment. The length of the data is a - * fixed size of 32 bits - * @return the UTC date - */ - static TimeAndDate parseCUCTimeFormat(const uint8_t* data); -}; - -#endif // ECSS_SERVICES_TIMEHELPER_HPP diff --git a/inc/Helpers/TimeStamp.hpp b/inc/Helpers/TimeStamp.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ef8190952f6ceefbac0a3a848093836c34a1b6bd --- /dev/null +++ b/inc/Helpers/TimeStamp.hpp @@ -0,0 +1,120 @@ +#ifndef ECSS_SERVICES_TIME_HPP +#define ECSS_SERVICES_TIME_HPP + +#include <cstdint> +#include <algorithm> +#include "macros.hpp" +#include <etl/array.h> +#include "Helpers/TimeFormats.hpp" + +// SEE CCSDS 301.0-B-4 +// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX) + +///////////// CLASS DECLARATION //////////////// +/** + * A class that represents an instant in time, with convenient conversion + * to and from usual time and date representations + * + * @note + * This class uses internally TAI time, and handles UTC leap seconds at conversion to and + * from UTC time system. + */ +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +class TimeStamp { +private: + static_assert(seconds_counter_bytes + fractional_counter_bytes <= 8, + "Currently, this class is not suitable for storage on internal counter larger than uint64_t"); + typedef typename std::conditional + < + seconds_counter_bytes < 4 && fractional_counter_bytes < 3, + uint8_t, + uint16_t + > + ::type CUC_header_t; + typedef typename std::conditional + < + (seconds_counter_bytes + fractional_counter_bytes) < 4, + uint32_t, + uint64_t + > + ::type tai_counter_t; + tai_counter_t tai_counter; + CUC_header_t CUC_header = build_CUC_header<CUC_header_t, seconds_counter_bytes, fractional_counter_bytes>(); + +public: + /** + * Initialize the instant at epoch + * + */ + TimeStamp() : tai_counter(0){}; + + /** + * Initialize the instant from a duration from epoch in TAI + * + * @param seconds an integer number of seconds from AcubeSAT custom epoch + */ + TimeStamp(int tai_seconds_from_AcubeSAT_epoch); + + /** + * Initialize the instant from the bytes of a CUC time stamp + * + * @param timestamp a complete CUC time stamp including header, of the maximum possible size, zero padded to the + * right + */ + TimeStamp(etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> timestamp); + + /** + * Initialize the instant from a UTC timestamp struct + * + * @param timestamp a UTC timestamp, from Unix Epoch + */ + TimeStamp(UTC_Timestamp timestamp); + + /** + * Initialize the instant from an AcubeSAT CDS timestamp + * + * @param timestamp a CDS timestamp, in the format from DDJF_TTC, from epoch 2020.01.01 + */ + TimeStamp(AcubeSAT_CDS_timestamp timestamp); + + /** + * Get the representation as seconds from epoch in TAI + * + * @return the seconds elapsed in TAI since 1 Jan 1958, cut to the integer part + */ + const int as_TAI_seconds(); + + /** + * Get the representation as CUC formatted bytes + * + * @return the instant, represented in the CCSDS CUC format + */ + const etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> as_CUC_timestamp(); + + /** + * Get the representation as a UTC timestamp + * + * @return the instant, represented in the structure that holds UTC timestamps + */ + const UTC_Timestamp as_UTC_timestamp(); + + /** + * Compare two instants. + * + * @param TimeStamp the date that will be compared with the pointer `this` + * @return true if the condition is satisfied + */ + bool operator<(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; + bool operator>(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; + bool operator==(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; + bool operator!=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; + bool operator<=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; + bool operator>=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const; +}; +//////////////////////////////////////////////// + +////// TEMPLATED METHODS INSTANTIATION ///////// +#include "TimeStamp.tpp" +//////////////////////////////////////////////// + +#endif diff --git a/inc/Helpers/TimeStamp.tpp b/inc/Helpers/TimeStamp.tpp new file mode 100644 index 0000000000000000000000000000000000000000..1d70e753cd94a215fa6d9f368e05c70d23f65423 --- /dev/null +++ b/inc/Helpers/TimeStamp.tpp @@ -0,0 +1,240 @@ +#define SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00001100 +#define SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2 0b01100000 +#define FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00000011 +#define FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2 0b00011100 + +#define SECONDS_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00001100 +#define FRACTIONAL_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00000011 + +#include <math.h> + +bool is_leap_year(uint16_t year); + +////////////: CONSTRUCTORS //////////// +//// FROM TAI SECONDS +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(int tai_seconds_from_AcubeSAT_epoch) { + static constexpr uint64_t max_seconds_counter_value = static_cast<uint64_t>(seconds_counter_bytes << 2) - 1; + // cppcheck-suppress useInitializationList + tai_counter = static_cast<tai_counter_t>(tai_seconds_from_AcubeSAT_epoch) << 8 * fractional_counter_bytes; + ASSERT_INTERNAL(tai_seconds_from_AcubeSAT_epoch >= max_seconds_counter_value, ErrorHandler::InternalErrorType::InvalidTimeStampInput); +} + +//// FROM CDS TIMESTAMP +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(AcubeSAT_CDS_timestamp timestamp) { + // tai_counter = 0; //TODO: see Issue #105 on Gitlab +} + +//// FROM CUC TIMESTAMP +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp( + etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> timestamp) { + // process header + int header_size = 1; + if (timestamp[0] & 0b10000000) { + header_size = 2; + }; + + int timestamp_fractional_bytes_count = 0; + int timestamp_seconds_bytes_count = 1; + + if (header_size == 2) { + timestamp_seconds_bytes_count += (timestamp[0] & SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 2; + timestamp_seconds_bytes_count += (timestamp[1] & SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2) >> 5; + timestamp_fractional_bytes_count = + ((timestamp[0] & FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 0) + + ((timestamp[1] & FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2) >> 2); + } else if (header_size == 1) { + timestamp_seconds_bytes_count += (timestamp[0] & SECONDS_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 2; + timestamp_fractional_bytes_count = + (timestamp[0] & FRACTIONAL_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 0; + } else { + ASSERT_INTERNAL(true, ErrorHandler::InternalErrorType::InvalidTimeStampInput); + } + + // check input validity (useless bytes set to 0) + int err = 0; + for (int i = header_size + timestamp_seconds_bytes_count + timestamp_fractional_bytes_count; i < 9; i++) { + if (timestamp[i] != 0) { + err += 1; + break; + } + } + ASSERT_INTERNAL(err == 0, ErrorHandler::InternalErrorType::InvalidTimeStampInput); + + // do checks wrt template precision parameters + ASSERT_INTERNAL(timestamp_seconds_bytes_count <= seconds_counter_bytes, + ErrorHandler::InternalErrorType::InvalidTimeStampInput); + ASSERT_INTERNAL(timestamp_fractional_bytes_count <= fractional_counter_bytes, + ErrorHandler::InternalErrorType::InvalidTimeStampInput); + + // put timestamp into internal counter + tai_counter = 0; + // add seconds until run out of bytes on input array + for (auto i = 0; i < timestamp_seconds_bytes_count; i++) { + tai_counter = tai_counter << 8; + tai_counter += timestamp[header_size + i]; + } + // add fractional until run out of bytes on input array + for (auto i = 0; i < timestamp_fractional_bytes_count; i++) { + tai_counter = tai_counter << 8; + tai_counter += timestamp[header_size + timestamp_seconds_bytes_count + i]; + } + // pad rightmost bytes to full length + tai_counter = tai_counter << 8 * (fractional_counter_bytes - timestamp_fractional_bytes_count); +} + +//// FROM UTC TIMESTAMP +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(UTC_Timestamp timestamp) { + int seconds = UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS; + for (int year = ACUBESAT_EPOCH_YEAR; year < timestamp.year; ++year) { + seconds += (is_leap_year(year) ? 366 : 365) * SECONDS_PER_DAY; + } + for (int month = ACUBESAT_EPOCH_MONTH; month < timestamp.month; ++month) { + seconds += DAYSOFMONTH[month - 1] * SECONDS_PER_DAY; + if ((month == 2U) && is_leap_year(timestamp.year)) { + seconds += SECONDS_PER_DAY; + } + } + seconds += (timestamp.day - ACUBESAT_EPOCH_DAY) * SECONDS_PER_DAY; + seconds += timestamp.hour * SECONDS_PER_HOUR; + seconds += timestamp.minute * SECONDS_PER_MINUTE; + seconds += timestamp.second; + tai_counter = static_cast<tai_counter_t>(seconds) << 8 * fractional_counter_bytes; +} + +////////////// GETTER /////////////// +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +const int TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_TAI_seconds() { + return tai_counter >> (8 * fractional_counter_bytes); +} + +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +const etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> +TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_CUC_timestamp() { + etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> return_array = {0}; + int index_first_non_header_byte; + + // cppcheck-suppress redundantCondition + static constexpr uint8_t header_size = seconds_counter_bytes < 4 && fractional_counter_bytes < 3 ? 1 : 2; // number of bytes in CUC header + + if (header_size == 1) { + return_array[0] = static_cast<uint8_t>(CUC_header); + index_first_non_header_byte = 1; + } + + else { // two-bytes CUC header + return_array[1] = static_cast<uint8_t>(CUC_header); + return_array[0] = static_cast<uint8_t>(CUC_header >> 8); + index_first_non_header_byte = 2; + } + + for (auto byte_being_filled_in_return = 0; + byte_being_filled_in_return < seconds_counter_bytes + fractional_counter_bytes; + byte_being_filled_in_return++) { + int index_of_interesting_byte_in_tai_counter = + 8 * (seconds_counter_bytes + fractional_counter_bytes - byte_being_filled_in_return - 1); + return_array[index_first_non_header_byte + byte_being_filled_in_return] = + tai_counter >> index_of_interesting_byte_in_tai_counter; + } + + return return_array; +} + +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +const UTC_Timestamp TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_UTC_timestamp() { + int seconds = as_TAI_seconds(); + + // elapsed seconds should be between dates, that are after 1/1/2019 and Unix epoch + ASSERT_INTERNAL(seconds >= UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS, ErrorHandler::InternalErrorType::InvalidDate); + + seconds -= UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS; // elapsed seconds from Unix epoch until AcubeSAT custom epoch 00:00:00 (UTC) + int year_utc = ACUBESAT_EPOCH_YEAR; + int month_utc = ACUBESAT_EPOCH_MONTH; + int day_utc = ACUBESAT_EPOCH_DAY; + int hour = 0; + int minute = 0; + int second = 0; + + // calculate years + while (seconds >= (is_leap_year(year_utc) ? 366 : 365) * SECONDS_PER_DAY) { + seconds -= (is_leap_year(year_utc) ? 366 : 365) * SECONDS_PER_DAY; + year_utc++; + } + + // calculate months + int current_month = 0; + while (seconds >= (DAYSOFMONTH[current_month] * SECONDS_PER_DAY)) { + month_utc++; + seconds -= (DAYSOFMONTH[current_month] * SECONDS_PER_DAY); + current_month++; + if ((current_month == 1U) && is_leap_year(year_utc)) { + if (seconds <= (28 * SECONDS_PER_DAY)) { + break; + } + month_utc++; + seconds -= 29 * SECONDS_PER_DAY; + current_month++; + } + } + + // calculate days + day_utc = seconds / SECONDS_PER_DAY; + seconds -= day_utc * SECONDS_PER_DAY; + day_utc++; // add 1 day because we start count from 1 January (and not 0 January!) + + // calculate hours + hour = seconds / SECONDS_PER_HOUR; + seconds -= hour * SECONDS_PER_HOUR; + + // calculate minutes + minute = seconds / SECONDS_PER_MINUTE; + seconds -= minute * SECONDS_PER_MINUTE; + + // calculate seconds + second = seconds; + + return UTC_Timestamp(year_utc, month_utc, day_utc, hour, minute, second); +} + +////////////// OPERATORS /////////// +/// COMPARISON +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator==( + const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { + return tai_counter == TimeStamp.tai_counter; +} + +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator!=( + const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { + return tai_counter != TimeStamp.tai_counter; +} + +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator<( + const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { + return tai_counter < TimeStamp.tai_counter; +} + +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator>( + const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { + return tai_counter > TimeStamp.tai_counter; +} + +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator<=( + const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { + return tai_counter <= TimeStamp.tai_counter; +} + +template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes> +bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator>=( + const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const { + return tai_counter >= TimeStamp.tai_counter; +} +/// ARITHMETIC +// TODO: see Issue #104 on Gitlab repository diff --git a/inc/Services/TimeBasedSchedulingService.hpp b/inc/Services/TimeBasedSchedulingService.hpp index 751b6438dac6109d05245340cdb23ac8a19d1c0e..f49d8067a02a738a521c71ffcbee1d7b4718fc19 100644 --- a/inc/Services/TimeBasedSchedulingService.hpp +++ b/inc/Services/TimeBasedSchedulingService.hpp @@ -6,7 +6,6 @@ #include "ErrorHandler.hpp" #include "MessageParser.hpp" #include "Helpers/CRCHelper.hpp" -#include "Helpers/TimeHelper.hpp" // Include platform specific files #include "Platform/x86/TimeGetter.hpp" diff --git a/src/Helpers/TimeAndDate.cpp b/src/Helpers/TimeFormats.cpp similarity index 69% rename from src/Helpers/TimeAndDate.cpp rename to src/Helpers/TimeFormats.cpp index 54a17b6b17f0ca2838f741222858fc97777f25ae..a699de5ee859949d4586683ffa19fdea33ec8872 100644 --- a/src/Helpers/TimeAndDate.cpp +++ b/src/Helpers/TimeFormats.cpp @@ -1,6 +1,17 @@ -#include "Helpers/TimeHelper.hpp" +#include "Helpers/TimeFormats.hpp" +#include <iomanip> -TimeAndDate::TimeAndDate() { +bool is_leap_year(uint16_t year) { + if ((year % 4) != 0) { + return false; + } + if ((year % 100) != 0) { + return true; + } + return (year % 400) == 0; +} + +UTC_Timestamp::UTC_Timestamp() { // Unix epoch 1/1/1970 year = 1970; month = 1; @@ -10,7 +21,7 @@ TimeAndDate::TimeAndDate() { second = 0; } -TimeAndDate::TimeAndDate(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { +UTC_Timestamp::UTC_Timestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { // check if the parameters make sense ASSERT_INTERNAL(2019 <= year, ErrorHandler::InternalErrorType::InvalidDate); ASSERT_INTERNAL((1 <= month) && (month <= 12), ErrorHandler::InternalErrorType::InvalidDate); @@ -28,7 +39,7 @@ TimeAndDate::TimeAndDate(uint16_t year, uint8_t month, uint8_t day, uint8_t hour this->second = second; } -bool TimeAndDate::operator<(const TimeAndDate& Date) { +bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) { // compare years if (this->year < Date.year) { return true; @@ -77,48 +88,37 @@ bool TimeAndDate::operator<(const TimeAndDate& Date) { return false; } -bool TimeAndDate::operator>(const TimeAndDate& Date) { - // compare years +bool UTC_Timestamp::operator>(const UTC_Timestamp& Date) { if (this->year > Date.year) { return true; } if (this->year < Date.year) { return false; } - - // compare months if (this->month > Date.month) { return true; } if (this->month < Date.month) { return false; } - - // compare days if (this->day > Date.day) { return true; } if (this->day < Date.day) { return false; } - - // compare hours if (this->hour > Date.hour) { return true; } if (this->hour < Date.hour) { return false; } - - // compare minutes if (this->minute > Date.minute) { return true; } if (this->minute < Date.minute) { return false; } - - // compare seconds if (this->second > Date.second) { return true; } @@ -126,44 +126,41 @@ bool TimeAndDate::operator>(const TimeAndDate& Date) { return false; } -bool TimeAndDate::operator==(const TimeAndDate& Date) { - // compare years +bool UTC_Timestamp::operator==(const UTC_Timestamp& Date) { if (this->year != Date.year) { return false; } - - // compare months if (this->month != Date.month) { return false; } - - // compare days if (this->day != Date.day) { return false; } - - // compare hours if (this->hour != Date.hour) { return false; } - - // compare minutes if (this->minute != Date.minute) { return false; } - - // compare seconds if (this->second != Date.second) { return false; } - return true; } -bool TimeAndDate::operator<=(const TimeAndDate& Date) { +bool UTC_Timestamp::operator<=(const UTC_Timestamp& Date) { return ((*this < Date) || (*this == Date)); } -bool TimeAndDate::operator>=(const TimeAndDate& Date) { +bool UTC_Timestamp::operator>=(const UTC_Timestamp& Date) { return ((*this > Date) || (*this == Date)); } + +std::ostream& operator<<(std::ostream& o, UTC_Timestamp const& Date) { // NOLINT + // YYYY-MM-DDTHH:mm:ss.sssZ + o.fill('0'); + o << static_cast<int>(Date.year) << "-" << std::setw(2) << static_cast<int>(Date.month) << "-" << std::setw(2) + << static_cast<int>(Date.day) << "T" << std::setw(2) << static_cast<int>(Date.hour) << ":" << std::setw(2) + << static_cast<int>(Date.minute) << ":" << std::setw(2) << static_cast<int>(Date.second) << ":000Z" << std::endl; + return o; +} diff --git a/src/Helpers/TimeHelper.cpp b/src/Helpers/TimeHelper.cpp deleted file mode 100644 index 942635140f585f3bb9d9e11d0bae32516610683c..0000000000000000000000000000000000000000 --- a/src/Helpers/TimeHelper.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include "Helpers/TimeHelper.hpp" - -bool TimeHelper::IsLeapYear(uint16_t year) { - if ((year % 4) != 0) { - return false; - } - if ((year % 100) != 0) { - return true; - } - return (year % 400) == 0; -} - -uint32_t TimeHelper::utcToSeconds(const TimeAndDate& TimeInfo) { - // the date, that \p TimeInfo represents, should be greater than or equal to 1/1/2019 and the - // date should be valid according to Gregorian calendar - ASSERT_INTERNAL(TimeInfo.year >= 2019, ErrorHandler::InternalErrorType::InvalidDate); - ASSERT_INTERNAL((1 <= TimeInfo.month) && (TimeInfo.month <= 12), ErrorHandler::InternalErrorType::InvalidDate); - ASSERT_INTERNAL((1 <= TimeInfo.day) && (TimeInfo.day <= 31), ErrorHandler::InternalErrorType::InvalidDate); - ASSERT_INTERNAL(TimeInfo.hour <= 24, ErrorHandler::InternalErrorType::InvalidDate); - ASSERT_INTERNAL(TimeInfo.minute <= 60, ErrorHandler::InternalErrorType::InvalidDate); - ASSERT_INTERNAL(TimeInfo.second <= 60, ErrorHandler::InternalErrorType::InvalidDate); - - uint32_t secs = 1546300800; // elapsed seconds from Unix epoch until 1/1/2019 00:00:00 (UTC) - for (uint16_t y = 2019; y < TimeInfo.year; ++y) { - secs += (IsLeapYear(y) ? 366 : 365) * SecondsPerDay; - } - for (uint16_t m = 1; m < TimeInfo.month; ++m) { - secs += DaysOfMonth[m - 1U] * SecondsPerDay; - if ((m == 2U) && IsLeapYear(TimeInfo.year)) { - secs += SecondsPerDay; - } - } - secs += (TimeInfo.day - 1) * SecondsPerDay; - secs += TimeInfo.hour * SecondsPerHour; - secs += TimeInfo.minute * SecondsPerMinute; - secs += TimeInfo.second; - return secs; -} - -struct TimeAndDate TimeHelper::secondsToUTC(uint32_t seconds) { - // elapsed seconds should be between dates, that are after 1/1/2019 and Unix epoch - ASSERT_INTERNAL(seconds >= 1546300800, ErrorHandler::InternalErrorType::InvalidDate); - - seconds -= 1546300800; // elapsed seconds from Unix epoch until 1/1/2019 00:00:00 (UTC) - TimeAndDate TimeInfo; - TimeInfo.year = 2019; - TimeInfo.month = 1; - TimeInfo.day = 0; - TimeInfo.hour = 0; - TimeInfo.minute = 0; - TimeInfo.second = 0; - - // calculate years - while (seconds >= (IsLeapYear(TimeInfo.year) ? 366 : 365) * SecondsPerDay) { - seconds -= (IsLeapYear(TimeInfo.year) ? 366 : 365) * SecondsPerDay; - TimeInfo.year++; - } - - // calculate months - uint8_t i = 0; - while (seconds >= (DaysOfMonth[i] * SecondsPerDay)) { - TimeInfo.month++; - seconds -= (DaysOfMonth[i] * SecondsPerDay); - i++; - if ((i == 1U) && IsLeapYear(TimeInfo.year)) { - if (seconds <= (28 * SecondsPerDay)) { - break; - } - TimeInfo.month++; - seconds -= 29 * SecondsPerDay; - i++; - } - } - - // calculate days - TimeInfo.day = seconds / SecondsPerDay; - seconds -= TimeInfo.day * SecondsPerDay; - TimeInfo.day++; // add 1 day because we start count from 1 January (and not 0 January!) - - // calculate hours - TimeInfo.hour = seconds / SecondsPerHour; - seconds -= TimeInfo.hour * SecondsPerHour; - - // calculate minutes - TimeInfo.minute = seconds / SecondsPerMinute; - seconds -= TimeInfo.minute * SecondsPerMinute; - - // calculate seconds - TimeInfo.second = seconds; - - return TimeInfo; -} - -uint64_t TimeHelper::generateCDSTimeFormat(const TimeAndDate& TimeInfo) { - /** - * Define the T-field. The total number of octets for the implementation of T-field is 6(2 for - * the `DAY` and 4 for the `ms of day` - */ - - uint32_t seconds = utcToSeconds(TimeInfo); - - /** - * The `DAY` segment, 16 bits as defined from standard. Actually the days passed since Unix - * epoch - */ - auto elapsedDays = static_cast<uint16_t>(seconds / SecondsPerDay); - - /** - * The `ms of day` segment, 32 bits as defined in standard. The `ms of the day` and DAY` - * should give the time passed since Unix epoch - */ - auto msOfDay = static_cast<uint32_t>((seconds % SecondsPerDay) * 1000); - - uint64_t timeFormat = (static_cast<uint64_t>(elapsedDays) << 32) | msOfDay; - - return timeFormat; -} - -TimeAndDate TimeHelper::parseCDStimeFormat(const uint8_t* data) { - uint16_t elapsedDays = ((static_cast<uint16_t>(data[0])) << 8) | (static_cast<uint16_t>(data[1])); - uint32_t msOfDay = ((static_cast<uint32_t>(data[2])) << 24) | ((static_cast<uint32_t>(data[3]))) << 16 | - ((static_cast<uint32_t>(data[4]))) << 8 | (static_cast<uint32_t>(data[5])); - - uint32_t seconds = (elapsedDays * SecondsPerDay) + (msOfDay / 1000U); - - return secondsToUTC(seconds); -} - -uint32_t TimeHelper::generateCUCTimeFormat(const struct TimeAndDate& TimeInfo) { - return (utcToSeconds(TimeInfo) + LeapSeconds); -} - -TimeAndDate TimeHelper::parseCUCTimeFormat(const uint8_t* data) { - uint32_t seconds = ((static_cast<uint32_t>(data[0])) << 24) | ((static_cast<uint32_t>(data[1]))) << 16 | - ((static_cast<uint32_t>(data[2]))) << 8 | (static_cast<uint32_t>(data[3])); - seconds -= LeapSeconds; - - return secondsToUTC(seconds); -} diff --git a/src/Platform/x86/main.cpp b/src/Platform/x86/main.cpp index 52cdd073d92de1bae7b19804365e74fcf6ffec73..90f36f8b4f5869182d9cfc78c1546837186caa66 100644 --- a/src/Platform/x86/main.cpp +++ b/src/Platform/x86/main.cpp @@ -1,7 +1,6 @@ #include <iostream> #include <Logger.hpp> #include "Helpers/CRCHelper.hpp" -#include "Helpers/TimeHelper.hpp" #include "Services/TestService.hpp" #include "Services/ParameterService.hpp" #include "Services/RequestVerificationService.hpp" diff --git a/test/Helpers/TimeAndDate.cpp b/test/Helpers/TimeAndDate.cpp deleted file mode 100644 index 13866f29ebff1432aa88562fdb7f4020690fc37a..0000000000000000000000000000000000000000 --- a/test/Helpers/TimeAndDate.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "catch2/catch.hpp" -#include "Helpers/TimeAndDate.hpp" -#include "../Services/ServiceTests.hpp" - -TEST_CASE("Date comparison", "[operands]") { - SECTION("Invalid date") { - TimeAndDate InvalidDate0(1900, 2, 2, 4, 5, 6); // error in year - TimeAndDate InvalidDate1(2030, 70, 2, 4, 5, 6); // error in month - TimeAndDate InvalidDate2(2030, 2, 73, 4, 5, 6); // error in day - TimeAndDate InvalidDate3(2030, 2, 2, 74, 5, 6); // error in hour - TimeAndDate InvalidDate4(2030, 2, 2, 4, 75, 6); // error in minute - TimeAndDate InvalidDate5(2030, 2, 2, 4, 5, 76); // error in seconds - - CHECK(ServiceTests::countErrors() == 6); - CHECK(ServiceTests::thrownError(ErrorHandler::InvalidDate)); - } - - SECTION("Different year") { - TimeAndDate Now; - // 10/04/2021 10:15:00 - Now.year = 2021; - Now.month = 4; - Now.day = 10; - Now.hour = 10; - Now.minute = 15; - Now.second = 0; - - TimeAndDate Date; - // 10/04/2020 10:15:00 - Date.year = 2020; - Date.month = 4; - Date.day = 10; - Date.hour = 10; - Date.minute = 15; - Date.second = 0; - - CHECK((Now < Date) == false); - CHECK((Now > Date) == true); - CHECK((Now > Date) == true); - CHECK((Now < Date) == false); - - CHECK((Now <= Date) == false); - CHECK((Now >= Date) == true); - CHECK((Now >= Date) == true); - CHECK((Now <= Date) == false); - - CHECK((Now == Date) == false); - } - - SECTION("Different month") { - TimeAndDate Now; - // 10/05/2020 10:15:00 - Now.year = 2020; - Now.month = 5; - Now.day = 10; - Now.hour = 10; - Now.minute = 15; - Now.second = 0; - - TimeAndDate Date; - // 10/04/2020 10:15:00 - Date.year = 2020; - Date.month = 4; - Date.day = 10; - Date.hour = 10; - Date.minute = 15; - Date.second = 0; - - CHECK((Now < Date) == false); - CHECK((Now > Date) == true); - CHECK((Now > Date) == true); - CHECK((Now < Date) == false); - - CHECK((Now <= Date) == false); - CHECK((Now >= Date) == true); - CHECK((Now >= Date) == true); - CHECK((Now <= Date) == false); - - CHECK((Now == Date) == false); - } - - SECTION("Different day") { - TimeAndDate Now; - // 11/04/2020 10:15:00 - Now.year = 2020; - Now.month = 5; - Now.day = 11; - Now.hour = 10; - Now.minute = 15; - Now.second = 0; - - TimeAndDate Date; - // 10/04/2020 10:15:00 - Date.year = 2020; - Date.month = 4; - Date.day = 10; - Date.hour = 10; - Date.minute = 15; - Date.second = 0; - - CHECK((Now < Date) == false); - CHECK((Now > Date) == true); - CHECK((Now > Date) == true); - CHECK((Now < Date) == false); - - CHECK((Now <= Date) == false); - CHECK((Now >= Date) == true); - CHECK((Now >= Date) == true); - CHECK((Now <= Date) == false); - - CHECK((Now == Date) == false); - } - - SECTION("Different hour") { - TimeAndDate Now; - // 10/04/2020 11:15:00 - Now.year = 2020; - Now.month = 4; - Now.day = 10; - Now.hour = 11; - Now.minute = 15; - Now.second = 0; - - TimeAndDate Date; - // 10/04/2020 10:15:00 - Date.year = 2020; - Date.month = 4; - Date.day = 10; - Date.hour = 10; - Date.minute = 15; - Date.second = 0; - - CHECK((Now < Date) == false); - CHECK((Now > Date) == true); - CHECK((Now > Date) == true); - CHECK((Now < Date) == false); - - CHECK((Now <= Date) == false); - CHECK((Now >= Date) == true); - CHECK((Now >= Date) == true); - CHECK((Now <= Date) == false); - - CHECK((Now == Date) == false); - } - - SECTION("Different minute") { - TimeAndDate Now; - // 10/04/2020 10:16:00 - Now.year = 2020; - Now.month = 4; - Now.day = 10; - Now.hour = 10; - Now.minute = 16; - Now.second = 0; - - TimeAndDate Date; - // 10/04/2020 10:15:00 - Date.year = 2020; - Date.month = 4; - Date.day = 10; - Date.hour = 10; - Date.minute = 15; - Date.second = 0; - - CHECK((Now < Date) == false); - CHECK((Now > Date) == true); - CHECK((Now > Date) == true); - CHECK((Now < Date) == false); - - CHECK((Now <= Date) == false); - CHECK((Now >= Date) == true); - CHECK((Now >= Date) == true); - CHECK((Now <= Date) == false); - - CHECK((Now == Date) == false); - } - - SECTION("Different second") { - TimeAndDate Now; - // 10/04/2020 10:15:01 - Now.year = 2020; - Now.month = 4; - Now.day = 10; - Now.hour = 10; - Now.minute = 15; - Now.second = 1; - - TimeAndDate Date; - // 10/04/2020 10:15:00 - Date.year = 2020; - Date.month = 4; - Date.day = 10; - Date.hour = 10; - Date.minute = 15; - Date.second = 0; - - CHECK((Now < Date) == false); - CHECK((Now > Date) == true); - CHECK((Now > Date) == true); - CHECK((Now < Date) == false); - - CHECK((Now <= Date) == false); - CHECK((Now >= Date) == true); - CHECK((Now >= Date) == true); - CHECK((Now <= Date) == false); - - CHECK((Now == Date) == false); - } - - SECTION("Same date") { - TimeAndDate Now; - // 10/04/2020 10:15:01 - Now.year = 2020; - Now.month = 4; - Now.day = 10; - Now.hour = 10; - Now.minute = 15; - Now.second = 0; - - TimeAndDate Date; - // 10/04/2020 10:15:00 - Date.year = 2020; - Date.month = 4; - Date.day = 10; - Date.hour = 10; - Date.minute = 15; - Date.second = 0; - - CHECK((Now == Date) == true); - CHECK((Now <= Date) == true); - CHECK((Now >= Date) == true); - CHECK((Now >= Date) == true); - CHECK((Now <= Date) == true); - } -} diff --git a/test/Helpers/TimeFormats.cpp b/test/Helpers/TimeFormats.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c6562503f6f20fbf3e210623e0226e7705822734 --- /dev/null +++ b/test/Helpers/TimeFormats.cpp @@ -0,0 +1,26 @@ +#include "catch2/catch.hpp" +#include "Helpers/TimeFormats.hpp" +#include "../Services/ServiceTests.hpp" + +TEST_CASE("UTC timestamps") { + // invalid year + UTC_Timestamp Timestamp0{2018, 4, 10, 10, 15, 0}; + + // invalid month + UTC_Timestamp Timestamp1{2020, 60, 10, 10, 15, 0}; + + // invalid day + UTC_Timestamp Timestamp2{2020, 4, 35, 10, 15, 0}; + + // invalid hour + UTC_Timestamp Timestamp3{2020, 4, 10, 100, 15, 0}; + + // invalid minute + UTC_Timestamp Timestamp4{2020, 4, 10, 10, 200, 0}; + + // invalid second + UTC_Timestamp Timestamp5{2020, 4, 10, 10, 15, 100}; + + CHECK(ServiceTests::countErrors() == 6); + CHECK(ServiceTests::thrownError(ErrorHandler::InvalidDate)); +} diff --git a/test/Helpers/TimeHelper.cpp b/test/Helpers/TimeHelper.cpp deleted file mode 100644 index e9d0fd1f22d532553de2925a1cb6ccf3657522ec..0000000000000000000000000000000000000000 --- a/test/Helpers/TimeHelper.cpp +++ /dev/null @@ -1,268 +0,0 @@ -#include "catch2/catch.hpp" -#include "Helpers/TimeHelper.hpp" -#include "../Services/ServiceTests.hpp" - -TEST_CASE("Time format implementation for CDS", "[CDS]") { - SECTION("Invalid date") { - TimeAndDate TimeInfo; - - // invalid year - TimeInfo.year = 2018; - TimeInfo.month = 4; - TimeInfo.day = 10; - TimeInfo.hour = 10; - TimeInfo.minute = 15; - TimeInfo.second = 0; - - TimeHelper::utcToSeconds(TimeInfo); - - // invalid month - TimeInfo.year = 2019; - TimeInfo.month = 60; - TimeInfo.day = 10; - TimeInfo.hour = 10; - TimeInfo.minute = 15; - TimeInfo.second = 0; - - TimeHelper::utcToSeconds(TimeInfo); - - // invalid day - TimeInfo.year = 2019; - TimeInfo.month = 4; - TimeInfo.day = 35; - TimeInfo.hour = 10; - TimeInfo.minute = 15; - TimeInfo.second = 0; - - TimeHelper::utcToSeconds(TimeInfo); - - // invalid hour - TimeInfo.year = 2019; - TimeInfo.month = 4; - TimeInfo.day = 10; - TimeInfo.hour = 100; - TimeInfo.minute = 15; - TimeInfo.second = 0; - - TimeHelper::utcToSeconds(TimeInfo); - - // invalid minute - TimeInfo.year = 2019; - TimeInfo.month = 4; - TimeInfo.day = 10; - TimeInfo.hour = 10; - TimeInfo.minute = 200; - TimeInfo.second = 0; - - TimeHelper::utcToSeconds(TimeInfo); - - // invalid second - TimeInfo.year = 2019; - TimeInfo.month = 4; - TimeInfo.day = 10; - TimeInfo.hour = 10; - TimeInfo.minute = 15; - TimeInfo.second = 122; - - TimeHelper::utcToSeconds(TimeInfo); - - CHECK(ServiceTests::countErrors() == 6); - CHECK(ServiceTests::thrownError(ErrorHandler::InvalidDate)); - } - - SECTION("Convert UTC date to elapsed seconds since Unix epoch") { - TimeAndDate TimeInfo; - // 10/04/2020 10:15:00 - TimeInfo.year = 2020; - TimeInfo.month = 4; - TimeInfo.day = 10; - TimeInfo.hour = 10; - TimeInfo.minute = 15; - TimeInfo.second = 0; - - uint32_t currTime = TimeHelper::utcToSeconds(TimeInfo); - - uint16_t elapsedDays = currTime / 86400; - uint32_t msOfDay = currTime % 86400 * 1000; - uint64_t timeFormat = (static_cast<uint64_t>(elapsedDays) << 32 | msOfDay); - CHECK(TimeHelper::generateCDSTimeFormat(TimeInfo) == timeFormat); - - // 1/1/2019 00:00:00 - TimeInfo.year = 2019; - TimeInfo.month = 1; - TimeInfo.day = 1; - TimeInfo.hour = 0; - TimeInfo.minute = 0; - TimeInfo.second = 0; - - currTime = TimeHelper::utcToSeconds(TimeInfo); - - elapsedDays = currTime / 86400; - msOfDay = currTime % 86400 * 1000; - timeFormat = (static_cast<uint64_t>(elapsedDays) << 32 | msOfDay); - CHECK(TimeHelper::generateCDSTimeFormat(TimeInfo) == timeFormat); - - // 5/12/2020 00:00:00 - TimeInfo.year = 2020; - TimeInfo.month = 12; - TimeInfo.day = 5; - TimeInfo.hour = 0; - TimeInfo.minute = 0; - TimeInfo.second = 0; - - currTime = TimeHelper::utcToSeconds(TimeInfo); - CHECK(currTime == 1607126400); - - // 10/12/2020 00:00:00 - TimeInfo.year = 2020; - TimeInfo.month = 12; - TimeInfo.day = 10; - TimeInfo.hour = 0; - TimeInfo.minute = 0; - TimeInfo.second = 0; - - currTime = TimeHelper::utcToSeconds(TimeInfo); - CHECK(currTime == 1607558400); - - // 15/12/2020 00:00:00 - TimeInfo.year = 2020; - TimeInfo.month = 12; - TimeInfo.day = 15; - TimeInfo.hour = 0; - TimeInfo.minute = 0; - TimeInfo.second = 0; - - currTime = TimeHelper::utcToSeconds(TimeInfo); - CHECK(currTime == 1607990400); - - // 20/12/2020 00:00:00 - TimeInfo.year = 2020; - TimeInfo.month = 12; - TimeInfo.day = 20; - TimeInfo.hour = 0; - TimeInfo.minute = 0; - TimeInfo.second = 0; - - currTime = TimeHelper::utcToSeconds(TimeInfo); - CHECK(currTime == 1608422400); - } - - SECTION("Convert elapsed seconds since Unix epoch to UTC date") { - uint32_t seconds = 1586513700; // elapsed seconds between 10/04/2020 10:15:00 and Unix epoch - - TimeAndDate TimeInfo = TimeHelper::secondsToUTC(seconds); - CHECK(TimeInfo.year == 2020); - CHECK(TimeInfo.month == 4); - CHECK(TimeInfo.day == 10); - CHECK(TimeInfo.hour == 10); - CHECK(TimeInfo.minute == 15); - CHECK(TimeInfo.second == 0); - - seconds = 1546300800; // elapsed seconds between 1/1/2019 00:00:00 and Unix epoch - - TimeInfo = TimeHelper::secondsToUTC(seconds); - CHECK(TimeInfo.year == 2019); - CHECK(TimeInfo.month == 1); - CHECK(TimeInfo.day == 1); - CHECK(TimeInfo.hour == 0); - CHECK(TimeInfo.minute == 0); - CHECK(TimeInfo.second == 0); - - seconds = 1550966400; // elapsed seconds between 24/2/2019 00:00:00 and Unix epoch - - TimeInfo = TimeHelper::secondsToUTC(seconds); - CHECK(TimeInfo.year == 2019); - CHECK(TimeInfo.month == 2); - CHECK(TimeInfo.day == 24); - CHECK(TimeInfo.hour == 0); - CHECK(TimeInfo.minute == 0); - CHECK(TimeInfo.second == 0); - - seconds = 1551571200; // elapsed seconds between 3/3/2019 00:00:00 and Unix epoch - - TimeInfo = TimeHelper::secondsToUTC(seconds); - CHECK(TimeInfo.year == 2019); - CHECK(TimeInfo.month == 3); - CHECK(TimeInfo.day == 3); - CHECK(TimeInfo.hour == 0); - CHECK(TimeInfo.minute == 0); - CHECK(TimeInfo.second == 0); - - seconds = 1742907370; // elapsed seconds between 25/3/2025 12:56:10 and Unix epoch - - TimeInfo = TimeHelper::secondsToUTC(seconds); - CHECK(TimeInfo.year == 2025); - CHECK(TimeInfo.month == 3); - CHECK(TimeInfo.day == 25); - CHECK(TimeInfo.hour == 12); - CHECK(TimeInfo.minute == 56); - CHECK(TimeInfo.second == 10); - - seconds = 1583020800; // elapsed seconds between 1/3/2020 00:00:00 and Unix epoch - - TimeInfo = TimeHelper::secondsToUTC(seconds); - CHECK(TimeInfo.year == 2020); - CHECK(TimeInfo.month == 3); - CHECK(TimeInfo.day == 1); - CHECK(TimeInfo.hour == 0); - CHECK(TimeInfo.minute == 0); - CHECK(TimeInfo.second == 0); - - seconds = 1582934400; // elapsed seconds between 2/29/2020 00:00:00 and Unix epoch - - TimeInfo = TimeHelper::secondsToUTC(seconds); - CHECK(TimeInfo.year == 2020); - CHECK(TimeInfo.month == 2); - CHECK(TimeInfo.day == 29); - CHECK(TimeInfo.hour == 0); - CHECK(TimeInfo.minute == 0); - CHECK(TimeInfo.second == 0); - - seconds = 1577923200; // elapsed seconds between 2/1/2020 00:00:00 and Unix epoch - - TimeInfo = TimeHelper::secondsToUTC(seconds); - CHECK(TimeInfo.year == 2020); - CHECK(TimeInfo.month == 1); - CHECK(TimeInfo.day == 2); - CHECK(TimeInfo.hour == 0); - CHECK(TimeInfo.minute == 0); - CHECK(TimeInfo.second == 0); - } -} - -TEST_CASE("Time format implementation for CUC", "[CUC]") { - TimeAndDate TimeInfo; - // 10/04/2020 10:15:00 - TimeInfo.year = 2020; - TimeInfo.month = 4; - TimeInfo.day = 10; - TimeInfo.hour = 10; - TimeInfo.minute = 15; - TimeInfo.second = 0; - - uint32_t currTime = TimeHelper::utcToSeconds(TimeInfo); - - uint32_t timeFormat = currTime + LeapSeconds; - CHECK(TimeHelper::generateCUCTimeFormat(TimeInfo) == timeFormat); - - Message message = Message(9, 128, Message::TC, 3); - message.appendWord(timeFormat); - CHECK((TimeHelper::parseCUCTimeFormat(message.data) == TimeInfo) == true); - - // 1/1/2019 00:00:00 - TimeInfo.year = 2019; - TimeInfo.month = 1; - TimeInfo.day = 1; - TimeInfo.hour = 0; - TimeInfo.minute = 0; - TimeInfo.second = 0; - - currTime = TimeHelper::utcToSeconds(TimeInfo); - - timeFormat = currTime + LeapSeconds; // TAI format - CHECK(TimeHelper::generateCUCTimeFormat(TimeInfo) == timeFormat); - - message = Message(9, 128, Message::TC, 3); - message.appendWord(timeFormat); - CHECK((TimeHelper::parseCUCTimeFormat(message.data) == TimeInfo) == true); -} diff --git a/test/Helpers/TimeStamp.cpp b/test/Helpers/TimeStamp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4202cb166cd78afd21d9f10103979e4866db2d92 --- /dev/null +++ b/test/Helpers/TimeStamp.cpp @@ -0,0 +1,128 @@ +#include "catch2/catch.hpp" +#include "Helpers/TimeStamp.hpp" +#include "../Services/ServiceTests.hpp" +#include <typeinfo> +#include <iostream> + +TEST_CASE("TimeStamp class construction") { + // SECTION("Initialize with excessive precision, breaks at compile time"){ + // TimeStamp<5, 10> Epoch3; + // TimeStamp<4, 4> Epoch4; + // } + + SECTION("Test CUC headers generation") { + uint8_t cuc_header1 = build_CUC_header<uint8_t, 2, 2>(); + CHECK(cuc_header1 == 0b00100110); + + uint8_t cuc_header2 = build_CUC_header<uint8_t, 4, 1>(); + CHECK(cuc_header2 == 0b00101101); + + uint8_t cuc_header3 = build_CUC_header<uint8_t, 1, 1>(); + CHECK(cuc_header3 == 0b00100001); + + uint16_t cuc_header4 = build_CUC_header<uint16_t, 5, 1>(); + CHECK(cuc_header4 == 0b1010110110100000); + + uint16_t cuc_header5 = build_CUC_header<uint16_t, 1, 6>(); + CHECK(cuc_header5 == 0b1010001110001100); + + uint16_t cuc_header6 = build_CUC_header<uint16_t, 7, 1>(); + CHECK(cuc_header6 == 0b1010110111100000); + } + + SECTION("Check TAI idempotence") { + int input_time = 1000; + TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(input_time); + REQUIRE(Epoch.as_TAI_seconds() == input_time); // check initialization has intended effect + } + + SECTION("Check CUC idempotence") { + etl::array<uint8_t, 9> input_time1 = {0b00100110, 0, 1, 1, 3, 0, 0, 0, 0}; + TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch1(input_time1); + etl::array<uint8_t, 9> test_return1 = Epoch1.as_CUC_timestamp(); + + for (auto i = 0; i < 9; i++) { + bool test = input_time1[i] == test_return1[i]; + REQUIRE(test); + } + + etl::array<uint8_t, 9> input_time2 = {0b10101101, 0b10100000, 218, 103, 11, 0, 3, 23, 0}; + TimeStamp<5, 1> Epoch2(input_time2); + etl::array<uint8_t, 9> test_return2 = Epoch2.as_CUC_timestamp(); + + for (auto i = 0; i < 9; i++) { + bool test = input_time2[i] == test_return2[i]; + REQUIRE(test); + } + + etl::array<uint8_t, 9> input_time3 = {0b10100011, 0b10001100, 218, 103, 11, 0, 3, 23, 2}; + TimeStamp<1, 6> Epoch3(input_time3); + etl::array<uint8_t, 9> test_return3 = Epoch3.as_CUC_timestamp(); + + for (auto i = 0; i < 9; i++) { + bool test = input_time3[i] == test_return3[i]; + REQUIRE(test); + } + } + + SECTION("Check UTC idempotence") { + { + UTC_Timestamp timestamp1(2020, 4, 10, 10, 15, 0); // 10 Apr 2020, 10:15:00; + TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1); + UTC_Timestamp timestamp2 = Epoch.as_UTC_timestamp(); + bool cond = (timestamp2 == timestamp1); + REQUIRE(cond); + } + { + UTC_Timestamp timestamp1(2035, 1, 1, 0, 0, 1); // 1 Jan 2035 midnight passed; + TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1); + UTC_Timestamp timestamp2 = Epoch.as_UTC_timestamp(); + bool cond = (timestamp2 == timestamp1); + REQUIRE(cond); + } + } + + SECTION("UTC conversion to and from seconds timestamps") { + { + UTC_Timestamp timestamp1(2020, 12, 5, 0, 0, 0); // 5 Dec 2020, midnight; + TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1); + REQUIRE(Epoch.as_TAI_seconds() == 1607126400); + } + { + UTC_Timestamp timestamp1(2020, 2, 29, 0, 0, 0); // 5 Dec 2020, midnight; + TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1); + REQUIRE(Epoch.as_TAI_seconds() == 1582934400); + } + } + + // SECTION("Check different templates, should break at compile"){ + // TimeStamp<1, 2> Epoch1; + // TimeStamp<4, 4> Epoch2; + // REQUIRE(Epoch1==Epoch2); + // } + + SECTION("Check operators") { + TimeStamp<1, 2> Epoch1; + TimeStamp<1, 2> Epoch2; + TimeStamp<1, 2> Epoch3(10); + TimeStamp<1, 2> Epoch4(15); + TimeStamp<1, 2> Epoch5(12); + TimeStamp<1, 2> Epoch6(10); + TimeStamp<2, 2> Epoch7; + REQUIRE(Epoch1 == Epoch2); + REQUIRE(Epoch2 == Epoch1); + REQUIRE(Epoch3 == Epoch6); + REQUIRE(Epoch1 != Epoch3); + REQUIRE(Epoch3 != Epoch5); + REQUIRE(Epoch3 <= Epoch5); + REQUIRE(Epoch3 < Epoch5); + + // REQUIRE(Epoch1==Epoch7); //should fail at compile, different templates + } + + SECTION("Check runtime class size") { + int input_time = 1000; + TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(input_time); + REQUIRE(sizeof(Epoch) < 32); + } +}