GCC Code Coverage Report


Directory: ./
File: Svc/TimeConverter/TimeConverter.cpp
Date: 2026-09-23 22:11:34
Exec Total Coverage
Lines: 0 114 0.0%
Functions: 0 14 0.0%
Branches: 0 120 0.0%

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