GCC Code Coverage Report


Directory: Svc/Ccsds/CfdpManager/
File: Chunk.cpp
Date: 2026-09-03 21:16:27
Exec Total Coverage
Lines: 115 141 81.6%
Functions: 13 14 92.9%
Branches: 52 86 60.5%

Line Branch Exec Source
1 // ======================================================================
2 // \title Chunk.cpp
3 // \brief CFDP chunks (sparse gap tracking) logic file
4 //
5 // This file is a port of the cf_chunks.c file from the
6 // NASA Core Flight System (cFS) CFDP (CF) Application,
7 // version 3.0.0, adapted for use within the F-Prime (F') framework.
8 //
9 // This class handles the complexity of sparse gap tracking so that
10 // the CFDP engine doesn't need to worry about it. Information is given
11 // to the class and when needed calculations are made internally to
12 // help the engine build NAK packets. Received NAK segment requests
13 // are stored in this class as well and used for re-transmit processing.
14 //
15 // ======================================================================
16 //
17 // NASA Docket No. GSC-18,447-1
18 //
19 // Copyright (c) 2019 United States Government as represented by the
20 // Administrator of the National Aeronautics and Space Administration.
21 // All Rights Reserved.
22 //
23 // Licensed under the Apache License, Version 2.0 (the "License"); you may
24 // not use this file except in compliance with the License. You may obtain
25 // a copy of the License at
26 //
27 // http://www.apache.org/licenses/LICENSE-2.0
28 //
29 // Unless required by applicable law or agreed to in writing, software
30 // distributed under the License is distributed on an "AS IS" BASIS,
31 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32 // See the License for the specific language governing permissions and
33 // limitations under the License.
34 //
35 // ======================================================================
36
37 #include <string.h>
38
39 #include <Fw/Types/Assert.hpp>
40
41 #include <Svc/Ccsds/CfdpManager/Chunk.hpp>
42
43 namespace Svc {
44 namespace Ccsds {
45 namespace Cfdp {
46
47 // ======================================================================
48 // CfdpChunkList Class Implementation
49 // ======================================================================
50
51 26202 CfdpChunkList::CfdpChunkList(ChunkIdx maxChunks, Chunk* chunkMem)
52 26202 : m_count(0), m_maxChunks(maxChunks), m_chunks(chunkMem) {
53 26202 FW_ASSERT(maxChunks > 0);
54 26202 FW_ASSERT(chunkMem != nullptr);
55 26202 reset();
56 26202 }
57
58 26220 void CfdpChunkList::reset() {
59 26220 m_count = 0;
60
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 26220 times.
26220 memset(m_chunks, 0, sizeof(*m_chunks) * m_maxChunks);
61 26220 }
62
63 33 void CfdpChunkList::add(FileSize offset, FileSize size) {
64 // A zero-length chunk covers no bytes and is not a received interval. It also violates the
65 // non-empty invariant assumed downstream by combineNext() (chunk_end > offset), so ignore it
66 // rather than asserting. This value can be derived from incoming protocol data (a FileData PDU
67 // whose payload length equals the encoded offset length), so it must not be treated as a bug.
68
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 30 times.
33 if (size == 0) {
69 3 return;
70 }
71
72 30 const Chunk chunk = {offset, size};
73 30 const ChunkIdx i = findInsertPosition(&chunk);
74
75 // PTFO: files won't be so big we need to gracefully handle overflow,
76 // and in that case the user should change everything in chunks
77 // to use 64-bit numbers
78 30 FW_ASSERT((offset + size) >= offset, static_cast<FwAssertArgType>(offset), static_cast<FwAssertArgType>(size));
79
80
1/1
✓ Branch 2 taken 30 times.
30 insert(i, &chunk);
81 }
82
83 72 const Chunk* CfdpChunkList::getFirstChunk() const {
84
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 68 times.
72 return m_count ? &m_chunks[0] : nullptr;
85 }
86
87 4 void CfdpChunkList::removeFromFirst(FileSize size) {
88 4 Chunk* chunk = &m_chunks[0]; /* front is always 0 */
89
90
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (size > chunk->size) {
91 size = chunk->size;
92 }
93 4 chunk->size -= size;
94
95
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 if (!chunk->size) {
96 4 eraseChunk(0);
97 } else {
98 chunk->offset += size;
99 }
100 4 }
101
102 12 U32 CfdpChunkList::computeGaps(ChunkIdx maxGaps,
103 FileSize total,
104 FileSize start,
105 GapComputeCallback callback,
106 void* opaque) const {
107 12 U32 ret = 0;
108 12 ChunkIdx i = 0;
109 FileSize next_off;
110 FileSize gap_start;
111 12 Chunk chunk;
112
113 /* a zero-length file holds no data, and therefore no gaps */
114
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
12 if (total == 0) {
115 2 return 0;
116 }
117
118 10 FW_ASSERT(start < total, static_cast<FwAssertArgType>(start), static_cast<FwAssertArgType>(total));
119
120 /* simple case: there is no chunk data, which means there is a single gap of the entire size */
121
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (!m_count) {
122 chunk.offset = 0;
123 chunk.size = total;
124 if (callback) {
125 callback(&chunk, opaque);
126 }
127 ret = 1;
128 } else {
129 /* Handle initial gap if needed */
130
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
10 if (start < m_chunks[0].offset) {
131 chunk.offset = start;
132 chunk.size = m_chunks[0].offset - start;
133 if (callback) {
134 callback(&chunk, opaque);
135 }
136 ret = 1;
137 }
138
139
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 1 times.
16 while ((ret < maxGaps) && (i < m_count)) {
140
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 8 times.
11 next_off = (i == (m_count - 1)) ? total : m_chunks[i + 1].offset;
141 11 gap_start = (m_chunks[i].offset + m_chunks[i].size);
142
143
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 chunk.offset = (gap_start > start) ? gap_start : start;
144 11 chunk.size = (next_off - chunk.offset);
145
146
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
11 if (gap_start >= total) {
147 5 break;
148
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 } else if (start < next_off) {
149 /* Only report if gap finishes after start */
150
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (callback) {
151
1/1
✓ Branch 1 taken 2 times.
2 callback(&chunk, opaque);
152 }
153 6 ++ret;
154 }
155 6 ++i;
156 }
157 }
158
159 10 return ret;
160 }
161
162 15 void CfdpChunkList::insertChunk(ChunkIdx index, const Chunk* chunk) {
163 15 FW_ASSERT(m_count < m_maxChunks, m_count, m_maxChunks);
164 15 FW_ASSERT(index <= m_count, index, m_count);
165
166
3/4
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 11 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
15 if (m_count && (index != m_count)) {
167 memmove(&m_chunks[index + 1], &m_chunks[index], sizeof(*chunk) * (m_count - index));
168 }
169 15 memcpy(&m_chunks[index], chunk, sizeof(*chunk));
170
171 15 ++m_count;
172 15 }
173
174 5 void CfdpChunkList::eraseChunk(ChunkIdx index) {
175 5 FW_ASSERT(m_count > 0);
176 5 FW_ASSERT(index < m_count, index, m_count);
177
178 /* to erase, move memory over the old one */
179
2/4
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
5 memmove(&m_chunks[index], &m_chunks[index + 1], sizeof(*m_chunks) * (m_count - 1 - index));
180 5 --m_count;
181 5 }
182
183 1 void CfdpChunkList::eraseRange(ChunkIdx start, ChunkIdx end) {
184 /* Sanity check */
185 1 FW_ASSERT(end <= m_count, end, m_count);
186
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (start < end) {
188 memmove(&m_chunks[start], &m_chunks[end], sizeof(*m_chunks) * (m_count - end));
189 m_count = static_cast<ChunkIdx>(m_count - static_cast<ChunkIdx>(end - start));
190 }
191 1 }
192
193 30 ChunkIdx CfdpChunkList::findInsertPosition(const Chunk* chunk) {
194 30 ChunkIdx first = 0;
195 ChunkIdx i;
196 30 ChunkIdx count = m_count;
197 ChunkIdx step;
198
199
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 30 times.
51 while (count > 0) {
200 21 i = first;
201 21 step = static_cast<ChunkIdx>(count / 2);
202 21 i = static_cast<ChunkIdx>(i + step);
203
2/2
✓ Branch 5 taken 19 times.
✓ Branch 6 taken 2 times.
21 if (m_chunks[i].offset < chunk->offset) {
204 19 first = static_cast<ChunkIdx>(i + 1);
205 19 count = static_cast<ChunkIdx>(count - static_cast<ChunkIdx>(step + 1));
206 } else {
207 2 count = step;
208 }
209 }
210
211 30 return first;
212 }
213
214 30 bool CfdpChunkList::combineNext(ChunkIdx i, const Chunk* chunk) {
215 30 ChunkIdx combined_i = i;
216 30 bool ret = false;
217 30 FileSize chunk_end = chunk->offset + chunk->size;
218
219 /* Assert no rollover, only possible as a bug */
220 30 FW_ASSERT(chunk_end > chunk->offset, static_cast<FwAssertArgType>(chunk_end),
221 static_cast<FwAssertArgType>(chunk->offset));
222
223 /* Determine how many can be combined */
224
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 29 times.
31 for (; combined_i < m_count; ++combined_i) {
225 /* Advance combine index until there is a gap between end and the next offset */
226
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
2 if (chunk_end < m_chunks[combined_i].offset) {
227 1 break;
228 }
229 }
230
231 /* If index advanced the range of chunks can be combined */
232
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 29 times.
30 if (i != combined_i) {
233 /* End is the max of last combined chunk end or new chunk end */
234 1 chunk_end = CfdpChunkMax(m_chunks[combined_i - 1].offset + m_chunks[combined_i - 1].size, chunk_end);
235
236 /* Use current slot as combined entry */
237 1 m_chunks[i].size = chunk_end - chunk->offset;
238 1 m_chunks[i].offset = chunk->offset;
239
240 /* Erase the rest of the combined chunks (if any) */
241 1 eraseRange(static_cast<ChunkIdx>(i + 1), combined_i);
242 1 ret = true;
243 }
244
245 30 return ret;
246 }
247
248 30 bool CfdpChunkList::combinePrevious(ChunkIdx i, const Chunk* chunk) {
249 Chunk* prev;
250 FileSize prev_end;
251 FileSize chunk_end;
252 30 bool ret = false;
253
254 30 FW_ASSERT(i <= m_maxChunks, i, m_maxChunks);
255
256 /* Only need to check if there is a previous */
257
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 11 times.
30 if (i > 0) {
258 19 chunk_end = chunk->offset + chunk->size;
259 19 prev = &m_chunks[i - 1];
260 19 prev_end = prev->offset + prev->size;
261
262 /* Check if start of new chunk is less than end of previous (overlaps) */
263
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 4 times.
19 if (chunk->offset <= prev_end) {
264 /* When combining, use the bigger of the two endings */
265
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (prev_end < chunk_end) {
266 /* Combine with previous chunk */
267 15 prev->size = chunk_end - prev->offset;
268 }
269 15 ret = true;
270 }
271 }
272 30 return ret;
273 }
274
275 30 void CfdpChunkList::insert(ChunkIdx i, const Chunk* chunk) {
276 ChunkIdx smallest_i;
277 Chunk* smallest_c;
278 30 bool next = combineNext(i, chunk);
279 bool combined;
280
281
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 29 times.
30 if (next) {
282 1 combined = combinePrevious(i, &m_chunks[i]);
283
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (combined) {
284 1 eraseChunk(i);
285 }
286 } else {
287 29 combined = combinePrevious(i, chunk);
288
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 14 times.
29 if (!combined) {
289
1/2
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
15 if (m_count < m_maxChunks) {
290 15 insertChunk(i, chunk);
291 } else {
292 smallest_i = findSmallestSize();
293 smallest_c = &m_chunks[smallest_i];
294 if (smallest_c->size < chunk->size) {
295 eraseChunk(smallest_i);
296 insertChunk(findInsertPosition(chunk), chunk);
297 }
298 }
299 }
300 }
301 30 }
302
303 ChunkIdx CfdpChunkList::findSmallestSize() const {
304 ChunkIdx i;
305 ChunkIdx smallest = 0;
306
307 for (i = 1; i < m_count; ++i) {
308 if (m_chunks[i].size < m_chunks[smallest].size) {
309 smallest = i;
310 }
311 }
312
313 return smallest;
314 }
315
316 } // namespace Cfdp
317 } // namespace Ccsds
318 } // namespace Svc
319