| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ====================================================================== | ||
| 2 | // \title TimeConverter.cpp | ||
| 3 | // \author devin | ||
| 4 | // \brief cpp file for TimeConverter component implementation class | ||
| 5 | // ====================================================================== | ||
| 6 | |||
| 7 | #include "Svc/TimeConverter/TimeConverter.hpp" | ||
| 8 | |||
| 9 | namespace Svc { | ||
| 10 | |||
| 11 | constexpr I64 TimeConverter::US_PER_SECOND; | ||
| 12 | constexpr I64 TimeConverter::MAX_TIME_US; | ||
| 13 | |||
| 14 | namespace { | ||
| 15 | //! Numeric value of a time base, used to order a pair canonically | ||
| 16 | ✗ | FwTimeBaseStoreType timeBaseValue(const TimeBase& timeBase) { | |
| 17 | ✗ | return static_cast<FwTimeBaseStoreType>(timeBase.e); | |
| 18 | } | ||
| 19 | |||
| 20 | //! A time base denotes a clock, rather than the absence of one or a wildcard | ||
| 21 | ✗ | bool usableTimeBase(const TimeBase& timeBase) { | |
| 22 | ✗ | return (timeBase.e != TimeBase::TB_NONE) && (timeBase.e != TimeBase::TB_DONT_CARE); | |
| 23 | } | ||
| 24 | |||
| 25 | //! A pair of time bases in canonical order, lesser numeric value first | ||
| 26 | ✗ | Svc::TimeBasePair canonicalPair(const TimeBase& from, const TimeBase& to) { | |
| 27 | ✗ | const bool ascending = timeBaseValue(from) < timeBaseValue(to); | |
| 28 | ✗ | return Svc::TimeBasePair(ascending ? from : to, ascending ? to : from); | |
| 29 | } | ||
| 30 | } // namespace | ||
| 31 | |||
| 32 | // ---------------------------------------------------------------------- | ||
| 33 | // Component construction and destruction | ||
| 34 | // ---------------------------------------------------------------------- | ||
| 35 | |||
| 36 | ✗ | TimeConverter ::TimeConverter(const char* const compName) : TimeConverterComponentBase(compName) {} | |
| 37 | |||
| 38 | ✗ | TimeConverter ::~TimeConverter() {} | |
| 39 | |||
| 40 | // ---------------------------------------------------------------------- | ||
| 41 | // Handler implementations for user-defined typed input ports | ||
| 42 | // ---------------------------------------------------------------------- | ||
| 43 | |||
| 44 | ✗ | void TimeConverter ::offsetUpdate_handler(FwIndexType portNum, const Svc::TimeOffset& offset) { | |
| 45 | // storeOffset reports every failure by event, and the port carries no status back | ||
| 46 | ✗ | (void)this->storeOffset(offset.get_from(), offset.get_to(), offset.get_offset_us()); | |
| 47 | ✗ | } | |
| 48 | |||
| 49 | ✗ | Svc::ConvertTimeStatus TimeConverter ::convertTime_handler(FwIndexType portNum, | |
| 50 | const Fw::Time& in_time, | ||
| 51 | Fw::Time& out_time) { | ||
| 52 | ✗ | const TimeBase in_tb = in_time.getTimeBase(); | |
| 53 | ✗ | const TimeBase out_tb = out_time.getTimeBase(); | |
| 54 | |||
| 55 | ✗ | if (this->checkTimeBases(in_tb, out_tb) != Fw::Success::SUCCESS) { | |
| 56 | ✗ | return Svc::ConvertTimeStatus::UNKNOWN_TIMEBASE; | |
| 57 | } | ||
| 58 | |||
| 59 | // A time is already in the requested base: no offset is needed | ||
| 60 | ✗ | if (timeBaseValue(in_tb) == timeBaseValue(out_tb)) { | |
| 61 | ✗ | out_time = in_time; | |
| 62 | ✗ | return Svc::ConvertTimeStatus::OK; | |
| 63 | } | ||
| 64 | |||
| 65 | ✗ | I64 offset_us = 0; | |
| 66 | ✗ | if (this->lookupOffset(in_tb, out_tb, offset_us) != Fw::Success::SUCCESS) { | |
| 67 | ✗ | this->log_WARNING_LO_NoConversionAvailable(in_tb, out_tb); | |
| 68 | ✗ | return Svc::ConvertTimeStatus::UNKNOWN_TIMEBASE; | |
| 69 | } | ||
| 70 | |||
| 71 | // Offsets are range-checked when stored, so this addition cannot overflow | ||
| 72 | ✗ | const I64 in_time_us = static_cast<I64>(in_time.getSeconds()) * US_PER_SECOND + in_time.getUSeconds(); | |
| 73 | ✗ | const I64 out_time_us = in_time_us + offset_us; | |
| 74 | ✗ | if ((out_time_us < 0) || (out_time_us > MAX_TIME_US)) { | |
| 75 | ✗ | this->log_WARNING_LO_InvalidTime(in_tb, out_tb, offset_us, in_time_us); | |
| 76 | ✗ | return Svc::ConvertTimeStatus::INVALID_TIME; | |
| 77 | } | ||
| 78 | |||
| 79 | ✗ | out_time.set(out_tb, in_time.getContext(), static_cast<U32>(out_time_us / US_PER_SECOND), | |
| 80 | ✗ | static_cast<U32>(out_time_us % US_PER_SECOND)); | |
| 81 | ✗ | return Svc::ConvertTimeStatus::OK; | |
| 82 | ✗ | } | |
| 83 | |||
| 84 | // ---------------------------------------------------------------------- | ||
| 85 | // Handler implementations for commands | ||
| 86 | // ---------------------------------------------------------------------- | ||
| 87 | |||
| 88 | ✗ | void TimeConverter ::SET_OFFSET_cmdHandler(FwOpcodeType opCode, | |
| 89 | U32 cmdSeq, | ||
| 90 | const TimeBase& from, | ||
| 91 | const TimeBase& to, | ||
| 92 | I64 offset_us) { | ||
| 93 | ✗ | Fw::CmdResponse response = Fw::CmdResponse::OK; | |
| 94 | ✗ | switch (this->storeOffset(from, to, offset_us)) { | |
| 95 | ✗ | case StoreStatus::OK: | |
| 96 | ✗ | this->log_ACTIVITY_HI_OffsetSet(from, to, offset_us); | |
| 97 | ✗ | break; | |
| 98 | // A full table is a state the operator clears, not a bad argument | ||
| 99 | ✗ | case StoreStatus::TABLE_FULL: | |
| 100 | ✗ | response = Fw::CmdResponse::EXECUTION_ERROR; | |
| 101 | ✗ | break; | |
| 102 | ✗ | case StoreStatus::INVALID: | |
| 103 | ✗ | response = Fw::CmdResponse::VALIDATION_ERROR; | |
| 104 | ✗ | break; | |
| 105 | } | ||
| 106 | ✗ | this->cmdResponse_out(opCode, cmdSeq, response); | |
| 107 | ✗ | } | |
| 108 | |||
| 109 | ✗ | void TimeConverter ::CLEAR_OFFSETS_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { | |
| 110 | ✗ | const FwSizeType cleared = this->m_offsets.getSize(); | |
| 111 | ✗ | this->m_offsets.clear(); | |
| 112 | // Clearing the table is the operator's recovery action, so re-arm the throttled warnings | ||
| 113 | ✗ | this->log_WARNING_LO_NoConversionAvailable_ThrottleClear(); | |
| 114 | ✗ | this->log_WARNING_LO_InvalidTime_ThrottleClear(); | |
| 115 | ✗ | this->log_WARNING_LO_IdenticalTimeBases_ThrottleClear(); | |
| 116 | ✗ | this->log_WARNING_HI_OffsetOutOfRange_ThrottleClear(); | |
| 117 | ✗ | this->log_WARNING_HI_OffsetTableFull_ThrottleClear(); | |
| 118 | ✗ | this->log_WARNING_HI_UnusableTimeBase_ThrottleClear(); | |
| 119 | ✗ | this->log_ACTIVITY_HI_OffsetsCleared(static_cast<U32>(cleared)); | |
| 120 | ✗ | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); | |
| 121 | ✗ | } | |
| 122 | |||
| 123 | ✗ | void TimeConverter ::GET_OFFSET_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, const TimeBase& from, const TimeBase& to) { | |
| 124 | ✗ | if (this->checkTimeBases(from, to) != Fw::Success::SUCCESS) { | |
| 125 | ✗ | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR); | |
| 126 | ✗ | return; | |
| 127 | } | ||
| 128 | |||
| 129 | ✗ | if (timeBaseValue(from) == timeBaseValue(to)) { | |
| 130 | ✗ | this->log_WARNING_LO_IdenticalTimeBases(from); | |
| 131 | ✗ | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR); | |
| 132 | ✗ | return; | |
| 133 | } | ||
| 134 | |||
| 135 | ✗ | I64 offset_us = 0; | |
| 136 | ✗ | if (this->lookupOffset(from, to, offset_us) != Fw::Success::SUCCESS) { | |
| 137 | ✗ | this->log_WARNING_LO_NoOffsetStored(from, to); | |
| 138 | ✗ | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); | |
| 139 | ✗ | return; | |
| 140 | } | ||
| 141 | |||
| 142 | ✗ | this->log_ACTIVITY_HI_OffsetReport(from, to, offset_us); | |
| 143 | ✗ | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); | |
| 144 | } | ||
| 145 | |||
| 146 | ✗ | void TimeConverter ::DUMP_OFFSETS_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { | |
| 147 | ✗ | if (this->m_offsets.getSize() == 0) { | |
| 148 | ✗ | this->log_ACTIVITY_HI_OffsetTableEmpty(); | |
| 149 | } | ||
| 150 | ✗ | for (auto it = this->m_offsets.begin(); it != this->m_offsets.end(); ++it) { | |
| 151 | ✗ | const Svc::TimeBasePair& pair = it->getKey(); | |
| 152 | ✗ | this->log_ACTIVITY_HI_OffsetReport(pair.get_lower(), pair.get_upper(), it->getValue()); | |
| 153 | ✗ | } | |
| 154 | ✗ | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); | |
| 155 | ✗ | } | |
| 156 | |||
| 157 | // ---------------------------------------------------------------------- | ||
| 158 | // Helper functions | ||
| 159 | // ---------------------------------------------------------------------- | ||
| 160 | |||
| 161 | ✗ | Fw::Success TimeConverter ::lookupOffset(const TimeBase& from, const TimeBase& to, I64& offset_us) const { | |
| 162 | ✗ | I64 stored_us = 0; | |
| 163 | ✗ | const Fw::Success status = this->m_offsets.find(canonicalPair(from, to), stored_us); | |
| 164 | ✗ | if (status == Fw::Success::SUCCESS) { | |
| 165 | // Offsets are stored in canonical order; the reverse conversion negates them | ||
| 166 | ✗ | offset_us = (timeBaseValue(from) < timeBaseValue(to)) ? stored_us : -stored_us; | |
| 167 | } | ||
| 168 | ✗ | return status; | |
| 169 | } | ||
| 170 | |||
| 171 | ✗ | TimeConverter::StoreStatus TimeConverter ::storeOffset(const TimeBase& from, const TimeBase& to, I64 offset_us) { | |
| 172 | ✗ | if (this->checkTimeBases(from, to) != Fw::Success::SUCCESS) { | |
| 173 | ✗ | return StoreStatus::INVALID; | |
| 174 | } | ||
| 175 | ✗ | if (timeBaseValue(from) == timeBaseValue(to)) { | |
| 176 | ✗ | this->log_WARNING_LO_IdenticalTimeBases(from); | |
| 177 | ✗ | return StoreStatus::INVALID; | |
| 178 | } | ||
| 179 | // Bound stored offsets so that applying or negating one cannot overflow | ||
| 180 | ✗ | if ((offset_us > MAX_TIME_US) || (offset_us < -MAX_TIME_US)) { | |
| 181 | ✗ | this->log_WARNING_HI_OffsetOutOfRange(from, to, offset_us); | |
| 182 | ✗ | return StoreStatus::INVALID; | |
| 183 | } | ||
| 184 | |||
| 185 | ✗ | const bool ascending = timeBaseValue(from) < timeBaseValue(to); | |
| 186 | ✗ | const I64 stored_us = ascending ? offset_us : -offset_us; | |
| 187 | |||
| 188 | // An insert replaces the entry for a pair already stored, and fails only when the table is full | ||
| 189 | ✗ | if (this->m_offsets.insert(canonicalPair(from, to), stored_us) != Fw::Success::SUCCESS) { | |
| 190 | ✗ | this->log_WARNING_HI_OffsetTableFull(from, to, static_cast<U32>(Svc::TimeConverterCfg::MAX_OFFSET_ENTRIES)); | |
| 191 | ✗ | return StoreStatus::TABLE_FULL; | |
| 192 | } | ||
| 193 | ✗ | return StoreStatus::OK; | |
| 194 | } | ||
| 195 | |||
| 196 | ✗ | Fw::Success TimeConverter ::checkTimeBases(const TimeBase& from, const TimeBase& to) { | |
| 197 | // TB_NONE and TB_DONT_CARE name no clock, so no offset relates them to one | ||
| 198 | ✗ | Fw::Success status = Fw::Success::SUCCESS; | |
| 199 | ✗ | if (!usableTimeBase(from)) { | |
| 200 | ✗ | this->log_WARNING_HI_UnusableTimeBase(from); | |
| 201 | ✗ | status = Fw::Success::FAILURE; | |
| 202 | } | ||
| 203 | ✗ | if (!usableTimeBase(to)) { | |
| 204 | ✗ | this->log_WARNING_HI_UnusableTimeBase(to); | |
| 205 | ✗ | status = Fw::Success::FAILURE; | |
| 206 | } | ||
| 207 | ✗ | return status; | |
| 208 | ✗ | } | |
| 209 | |||
| 210 | } // namespace Svc | ||
| 211 |