GCC Code Coverage Report


Directory: ./
File: SocketComponentHelper.cpp
Date: 2026-09-03 22:12:41
Exec Total Coverage
Lines: 196 222 88.3%
Functions: 23 24 95.8%
Branches: 123 155 79.4%

Line Branch Exec Source
1 // ======================================================================
2 // \title SocketComponentHelper.cpp
3 // \author mstarch, crsmith
4 // \brief cpp file for SocketComponentHelper implementation class
5 //
6 // \copyright
7 // Copyright 2009-2020, by the California Institute of Technology.
8 // ALL RIGHTS RESERVED. United States Government Sponsorship
9 // acknowledged.
10 //
11 // ======================================================================
12
13 #include <Drv/Ip/SocketComponentHelper.hpp>
14 #include <Fw/Logger/Logger.hpp>
15 #include <Fw/Types/Assert.hpp>
16 #include <cerrno>
17
18 namespace Drv {
19
20
5/5
✓ Branch 3 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
✓ Branch 12 taken 1 times.
✓ Branch 15 taken 1 times.
1 SocketComponentHelper::SocketComponentHelper() {}
21
22 2 SocketComponentHelper::~SocketComponentHelper() {}
23
24 1 void SocketComponentHelper::start(const Fw::ConstStringBase& name,
25 const FwTaskPriorityType priority,
26 const Os::Task::ParamType stack,
27 const Os::Task::ParamType cpuAffinity,
28 const FwTaskPriorityType priorityReconnect,
29 const Os::Task::ParamType stackReconnect,
30 const Os::Task::ParamType cpuAffinityReconnect) {
31 // Reconnect Thread
32 1 FW_ASSERT(m_reconnectTask.getState() ==
33 Os::Task::State::NOT_STARTED); // It is a coding error to start this task multiple times
34 1 this->m_reconnectStop = false;
35
1/1
✓ Branch 1 taken 1 times.
1 Fw::String reconnectName;
36
2/2
✓ Branch 1 taken 1 times.
✓ Branch 4 taken 1 times.
1 (void)reconnectName.format("%s_reconnect", name.toChar()); // task name may safely truncate
37 Os::Task::Arguments reconnectArguments(reconnectName, SocketComponentHelper::reconnectTask, this, priorityReconnect,
38
1/1
✓ Branch 1 taken 1 times.
1 stackReconnect, cpuAffinityReconnect);
39
1/1
✓ Branch 1 taken 1 times.
1 Os::Task::Status reconnectStat = m_reconnectTask.start(reconnectArguments);
40 1 FW_ASSERT(Os::Task::OP_OK == reconnectStat, static_cast<FwAssertArgType>(reconnectStat));
41
42 // Read Thread
43 1 FW_ASSERT(m_task.getState() ==
44 Os::Task::State::NOT_STARTED); // It is a coding error to start this task multiple times
45 1 this->m_stop = false;
46 // Note: the first step is for the IP socket to open the port
47
1/1
✓ Branch 1 taken 1 times.
1 Os::Task::Arguments arguments(name, SocketComponentHelper::readTask, this, priority, stack, cpuAffinity);
48
1/1
✓ Branch 1 taken 1 times.
1 Os::Task::Status stat = m_task.start(arguments);
49 1 FW_ASSERT(Os::Task::OP_OK == stat, static_cast<FwAssertArgType>(stat));
50 1 }
51
52 2 SocketIpStatus SocketComponentHelper::open() {
53 2 SocketIpStatus status = SOCK_ANOTHER_THREAD_OPENING;
54 2 OpenState local_open = OpenState::OPEN;
55 // Scope to guard lock
56 {
57
1/1
✓ Branch 1 taken 2 times.
2 Os::ScopeLock scopeLock(m_lock);
58
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (this->m_open == OpenState::NOT_OPEN) {
59 2 this->m_open = OpenState::OPENING;
60 2 local_open = this->m_open;
61 } else {
62 local_open = OpenState::SKIP;
63 }
64 2 }
65
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (local_open == OpenState::OPENING) {
66 // Open into a local descriptor and publish it under the lock
67 2 SocketDescriptor descriptor;
68 {
69
1/1
✓ Branch 1 taken 2 times.
2 Os::ScopeLock scopeLock(m_lock);
70 2 descriptor = this->m_descriptor;
71 2 }
72 2 FW_ASSERT(descriptor.fd == -1); // Ensure we are not opening an opened socket
73
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 status = this->getSocketHandler().open(descriptor);
74 // Lock scope
75 {
76
1/1
✓ Branch 1 taken 2 times.
2 Os::ScopeLock scopeLock(m_lock);
77
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (Drv::SOCK_SUCCESS == status) {
78 1 this->m_descriptor = descriptor;
79 1 this->m_open = OpenState::OPEN;
80 } else {
81 1 this->m_open = OpenState::NOT_OPEN;
82 1 this->m_descriptor.fd = -1;
83 }
84 2 }
85 // Notify connection on success outside locked scope
86
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (Drv::SOCK_SUCCESS == status) {
87
1/1
✓ Branch 1 taken 1 times.
1 this->connected();
88 }
89 }
90
91 2 return status;
92 }
93
94 1236 bool SocketComponentHelper::isOpened() {
95
1/1
✓ Branch 1 taken 1236 times.
1236 Os::ScopeLock scopedLock(this->m_lock);
96 1236 bool is_open = this->m_open == OpenState::OPEN;
97 1236 return is_open;
98 1236 }
99
100 void SocketComponentHelper::setAutomaticOpen(bool auto_open) {
101 Os::ScopeLock scopedLock(this->m_lock);
102 this->m_reopen = auto_open;
103 }
104
105 5 bool SocketComponentHelper::getAutomaticOpen() {
106
1/1
✓ Branch 1 taken 5 times.
5 Os::ScopeLock scopedLock(this->m_lock);
107 5 return this->m_reopen;
108 5 }
109
110 2 SocketIpStatus SocketComponentHelper::reopen() {
111 2 SocketIpStatus status = SOCK_SUCCESS;
112
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (not this->isOpened()) {
113 // Check for auto-open before attempting to reopen
114 2 bool reopen = this->getAutomaticOpen();
115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (not reopen) {
116 status = SOCK_AUTO_CONNECT_DISABLED;
117 // Open a network connection if it has not already been open
118 } else {
119 2 status = this->open();
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (status == SocketIpStatus::SOCK_ANOTHER_THREAD_OPENING) {
121 status = SocketIpStatus::SOCK_SUCCESS;
122 }
123 }
124 }
125 2 return status;
126 }
127
128 229 SocketIpStatus SocketComponentHelper::send(const U8* const data, const FwSizeType size) {
129 229 SocketIpStatus status = SOCK_SUCCESS;
130
1/1
✓ Branch 1 taken 229 times.
229 this->m_lock.lock();
131 229 SocketDescriptor descriptor = this->m_descriptor;
132
1/1
✓ Branch 1 taken 229 times.
229 this->m_lock.unlock();
133 // Prevent transmission before connection, or after a disconnect
134
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 229 times.
229 if (descriptor.fd == -1) {
135 this->requestReconnect();
136 SocketIpStatus reconnectStat = this->waitForReconnect();
137 if (reconnectStat == SOCK_SUCCESS) {
138 // Refresh local copy after reopen
139 this->m_lock.lock();
140 descriptor = this->m_descriptor;
141 this->m_lock.unlock();
142 } else {
143 return reconnectStat;
144 }
145 }
146
2/2
✓ Branch 1 taken 229 times.
✓ Branch 4 taken 229 times.
229 status = this->getSocketHandler().send(descriptor, data, size);
147
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 229 times.
229 if (status == SOCK_DISCONNECTED) {
148 this->close();
149 }
150 229 return status;
151 }
152
153 1 void SocketComponentHelper::shutdown() {
154
1/1
✓ Branch 1 taken 1 times.
1 Os::ScopeLock scopedLock(this->m_lock);
155
2/2
✓ Branch 1 taken 1 times.
✓ Branch 4 taken 1 times.
1 this->getSocketHandler().shutdown(this->m_descriptor);
156 1 }
157
158 3 void SocketComponentHelper::close() {
159
1/1
✓ Branch 1 taken 3 times.
3 Os::ScopeLock scopedLock(this->m_lock);
160
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
3 this->getSocketHandler().close(this->m_descriptor);
161 3 this->m_descriptor.fd = -1;
162 3 this->m_open = OpenState::NOT_OPEN;
163 3 }
164
165 /* Read Thread */
166
167 1 Os::Task::Status SocketComponentHelper::join() {
168 1 Os::Task::Status stat = m_task.join();
169 1 Os::Task::Status reconnectStat = this->joinReconnect();
170
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (stat == Os::Task::Status::OP_OK) {
171 1 return reconnectStat;
172 }
173 return stat;
174 }
175
176 1 void SocketComponentHelper::stop() {
177 // Scope to protect lock
178 {
179
1/1
✓ Branch 1 taken 1 times.
1 Os::ScopeLock scopeLock(m_lock);
180 1 this->m_stop = true;
181 1 }
182 1 this->stopReconnect();
183 1 this->shutdown(); // Break out of any receives and fully shutdown
184 1 }
185
186 1233 bool SocketComponentHelper::running() {
187
1/1
✓ Branch 1 taken 1233 times.
1233 Os::ScopeLock scopedLock(this->m_lock);
188 1233 bool running = not this->m_stop;
189 1233 return running;
190 1233 }
191
192 615 SocketIpStatus SocketComponentHelper::recv(U8* data, FwSizeType& size) {
193 615 SocketIpStatus status = SOCK_SUCCESS;
194 // Check for previously disconnected socket
195
1/1
✓ Branch 1 taken 615 times.
615 this->m_lock.lock();
196 615 SocketDescriptor descriptor = this->m_descriptor;
197
1/1
✓ Branch 1 taken 615 times.
615 this->m_lock.unlock();
198
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
615 if (descriptor.fd == -1) {
199 return SOCK_DISCONNECTED;
200 }
201
2/2
✓ Branch 1 taken 615 times.
✓ Branch 4 taken 615 times.
615 status = this->getSocketHandler().recv(descriptor, data, size);
202
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 614 times.
615 if (status == SOCK_DISCONNECTED) {
203
1/1
✓ Branch 1 taken 1 times.
1 this->close();
204 }
205 615 return status;
206 }
207
208 1 void SocketComponentHelper::readLoop() {
209 1 SocketIpStatus status = SOCK_SUCCESS;
210 do {
211 // Prevent transmission before connection, or after a disconnect
212
5/6
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 614 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 614 times.
616 if ((not this->isOpened()) and this->running()) {
213 2 this->requestReconnect();
214
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 status = this->waitForReconnect();
215 // When reopen is disabled, just break as this is a exit condition for the loop
216
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (status == SOCK_AUTO_CONNECT_DISABLED) {
217 break;
218 }
219 }
220 // If the network connection is open, read from it
221
5/6
✓ Branch 1 taken 615 times.
✓ Branch 2 taken 1 times.
✓ Branch 4 taken 615 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 615 times.
✓ Branch 7 taken 1 times.
616 if (this->isOpened() and this->running()) {
222
1/1
✓ Branch 1 taken 615 times.
615 Fw::Buffer buffer = this->getBuffer();
223
2/3
✓ Branch 1 taken 615 times.
✓ Branch 3 taken 615 times.
✗ Branch 4 not taken.
615 if (buffer.isValid()) {
224
1/1
✓ Branch 1 taken 615 times.
615 U8* data = buffer.getData();
225 615 FW_ASSERT(data != nullptr);
226
1/1
✓ Branch 1 taken 615 times.
615 FwSizeType size = buffer.getSize();
227 // recv blocks, so it may have been a while since its done an isOpened check
228
1/1
✓ Branch 1 taken 615 times.
615 status = this->recv(data, size);
229
4/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 614 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
615 if ((status != SOCK_SUCCESS) && (status != SOCK_INTERRUPTED_TRY_AGAIN) &&
230 (status != SOCK_NO_DATA_AVAILABLE)) {
231
1/1
✓ Branch 2 taken 1 times.
1 Fw::Logger::log("[WARNING] %s failed to recv from port with status %d and errno %d\n",
232
1/1
✓ Branch 1 taken 1 times.
2 this->m_task.getName().toChar(), status, errno);
233
1/1
✓ Branch 1 taken 1 times.
1 this->close();
234
1/1
✓ Branch 1 taken 1 times.
1 buffer.setSize(0);
235 } else {
236 // Send out received data
237
1/1
✓ Branch 1 taken 614 times.
614 buffer.setSize(size);
238 }
239
2/2
✓ Branch 1 taken 615 times.
✓ Branch 4 taken 615 times.
615 this->sendBuffer(buffer, status);
240 } else {
241 Fw::Logger::log("[WARNING] %s failed to get buffer for recv\n", this->m_task.getName().toChar());
242 (void)Os::Task::delay(SOCKET_RETRY_INTERVAL);
243 }
244 615 }
245 }
246 // This will loop until stopped. If auto-open is disabled, this will break when reopen returns disabled status
247
2/2
✓ Branch 1 taken 615 times.
✓ Branch 2 taken 1 times.
616 while (this->running());
248 // Close the socket
249 1 this->close(); // Close the port entirely
250 1 }
251
252 1 void SocketComponentHelper::readTask(void* pointer) {
253 1 FW_ASSERT(pointer != nullptr);
254 1 SocketComponentHelper* self = reinterpret_cast<SocketComponentHelper*>(pointer);
255 1 self->readLoop();
256 1 }
257
258 /* Reconnect Thread */
259
260 1 Os::Task::Status SocketComponentHelper::joinReconnect() {
261 1 return m_reconnectTask.join();
262 }
263
264 1 void SocketComponentHelper::stopReconnect() {
265
1/1
✓ Branch 1 taken 1 times.
1 Os::ScopeLock scopeLock(this->m_reconnectLock);
266 1 this->m_reconnectState = ReconnectState::NOT_RECONNECTING;
267 1 this->m_reconnectStop = true;
268 1 }
269
270 4695 bool SocketComponentHelper::runningReconnect() {
271
1/1
✓ Branch 1 taken 4695 times.
4695 Os::ScopeLock scopedLock(this->m_reconnectLock);
272 4695 bool running = not this->m_reconnectStop;
273 4695 return running;
274 4695 }
275
276 1 void SocketComponentHelper::reconnectLoop() {
277 1 SocketIpStatus status = SOCK_SUCCESS;
278 // @non-terminating@: runs until the reconnect thread is stopped
279
2/2
✓ Branch 1 taken 4694 times.
✓ Branch 2 taken 1 times.
4695 while (this->runningReconnect()) {
280 // Check if we need to reconnect
281 4694 bool reconnect = false;
282 {
283
1/1
✓ Branch 1 taken 4694 times.
4694 Os::ScopeLock scopedLock(this->m_reconnectLock);
284 4694 FW_ASSERT(this->m_reconnectState == ReconnectState::NOT_RECONNECTING ||
285 this->m_reconnectState == ReconnectState::REQUEST_RECONNECT ||
286 this->m_reconnectState == ReconnectState::RECONNECT_IN_PROGRESS,
287 static_cast<FwAssertArgType>(this->m_reconnectState));
288
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4693 times.
4694 if (this->m_reconnectState == ReconnectState::REQUEST_RECONNECT) {
289 1 this->m_reconnectState = ReconnectState::RECONNECT_IN_PROGRESS;
290 1 reconnect = true;
291
292 }
293 // If we were already in or are now in RECONNECT_IN_PROGRESS we
294 // need to try to reconnect, again
295
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4692 times.
4693 else if (this->m_reconnectState == ReconnectState::RECONNECT_IN_PROGRESS) {
296 1 reconnect = true;
297 }
298 4694 }
299
300
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4692 times.
4694 if (reconnect) {
301 2 status = this->reopen();
302
303 // Reopen Case 1: Auto Connect is disabled, so no longer
304 // try to reconnect
305
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (status == SOCK_AUTO_CONNECT_DISABLED) {
306 Os::ScopeLock scopedLock(this->m_reconnectLock);
307 this->m_reconnectState = ReconnectState::NOT_RECONNECTING;
308 }
309 // Reopen Case 2: Success, so no longer
310 // try to reconnect
311
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 else if (status == SOCK_SUCCESS) {
312
1/1
✓ Branch 1 taken 1 times.
1 Os::ScopeLock scopedLock(this->m_reconnectLock);
313 1 this->m_reconnectState = ReconnectState::NOT_RECONNECTING;
314 1 }
315 // Reopen Case 3: Keep trying to reconnect - NO reconnect
316 // state change
317 else {
318
1/1
✓ Branch 2 taken 1 times.
1 Fw::Logger::log("[WARNING] %s failed to open port with status %d and errno %d\n",
319
1/1
✓ Branch 1 taken 1 times.
2 this->m_task.getName().toChar(), status, errno);
320 1 (void)Os::Task::delay(SOCKET_RETRY_INTERVAL);
321 }
322 } else {
323 // After a brief delay, we will loop again
324 4692 (void)Os::Task::delay(this->m_reconnectCheckInterval);
325 }
326 }
327 1 }
328
329 1 void SocketComponentHelper::reconnectTask(void* pointer) {
330 1 FW_ASSERT(pointer != nullptr);
331 1 SocketComponentHelper* self = reinterpret_cast<SocketComponentHelper*>(pointer);
332 1 self->reconnectLoop();
333 1 }
334
335 2 void SocketComponentHelper::requestReconnect() {
336
1/1
✓ Branch 1 taken 2 times.
2 Os::ScopeLock scopedLock(this->m_reconnectLock);
337
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (m_reconnectState == ReconnectState::NOT_RECONNECTING) {
338 1 m_reconnectState = ReconnectState::REQUEST_RECONNECT;
339 }
340 4 return;
341 2 }
342
343 2 SocketIpStatus SocketComponentHelper::waitForReconnect(Fw::TimeInterval timeout) {
344 // Do not attempt to reconnect if auto reconnect config flag is disabled
345
2/3
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
2 if (!this->getAutomaticOpen()) {
346 return SOCK_AUTO_CONNECT_DISABLED;
347 }
348
349
1/1
✓ Branch 1 taken 2 times.
2 Fw::TimeInterval elapsed = Fw::TimeInterval(0, 0);
350
351
3/3
✓ Branch 1 taken 107 times.
✓ Branch 3 taken 106 times.
✓ Branch 4 taken 1 times.
107 while (elapsed < timeout) {
352 // If the reconnect thread is NOT reconnecting, we are done waiting
353 // If we are no longer running the reconnect thread, we are done waiting
354 {
355
1/1
✓ Branch 1 taken 106 times.
106 Os::ScopeLock scopedLock(this->m_reconnectLock);
356
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 105 times.
106 if (this->m_reconnectState == ReconnectState::NOT_RECONNECTING) {
357 1 break;
358 }
359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (this->m_reconnectStop) {
360 break;
361 }
362 106 }
363 // Wait a bit before checking again
364
1/1
✓ Branch 1 taken 105 times.
105 (void)Os::Task::delay(this->m_reconnectWaitInterval);
365
3/3
✓ Branch 1 taken 105 times.
✓ Branch 4 taken 105 times.
✓ Branch 7 taken 105 times.
105 elapsed.add(this->m_reconnectWaitInterval.getSeconds(), this->m_reconnectWaitInterval.getUSeconds());
366 }
367
368 // If we have completed our loop, check if we are connected or if
369 // auto connect was disabled during our wait
370
3/3
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
2 if (this->isOpened()) {
371 1 return SOCK_SUCCESS;
372 }
373
374 // Check one more time if auto reconnect config flag got disabled
375
2/3
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if (!this->getAutomaticOpen()) {
376 return SOCK_AUTO_CONNECT_DISABLED;
377 }
378
379 1 return SOCK_DISCONNECTED; // Indicates failure of this attempt, another reopen needed
380 2 }
381
382 } // namespace Drv
383