F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
Chunk.cpp
Go to the documentation of this file.
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 
42 
43 namespace Svc {
44 namespace Ccsds {
45 namespace Cfdp {
46 
47 // ======================================================================
48 // CfdpChunkList Class Implementation
49 // ======================================================================
50 
52  : m_count(0), m_maxChunks(maxChunks), m_chunks(chunkMem) {
53  FW_ASSERT(maxChunks > 0);
54  FW_ASSERT(chunkMem != nullptr);
55  reset();
56 }
57 
59  m_count = 0;
60  memset(m_chunks, 0, sizeof(*m_chunks) * m_maxChunks);
61 }
62 
63 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  if (size == 0) {
69  return;
70  }
71 
72  const Chunk chunk = {offset, size};
73  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  FW_ASSERT((offset + size) >= offset, static_cast<FwAssertArgType>(offset), static_cast<FwAssertArgType>(size));
79 
80  insert(i, &chunk);
81 }
82 
84  return m_count ? &m_chunks[0] : nullptr;
85 }
86 
88  Chunk* chunk = &m_chunks[0]; /* front is always 0 */
89 
90  if (size > chunk->size) {
91  size = chunk->size;
92  }
93  chunk->size -= size;
94 
95  if (!chunk->size) {
96  eraseChunk(0);
97  } else {
98  chunk->offset += size;
99  }
100 }
101 
103  FileSize total,
104  FileSize start,
105  GapComputeCallback callback,
106  void* opaque) const {
107  U32 ret = 0;
108  ChunkIdx i = 0;
109  FileSize next_off;
110  FileSize gap_start;
111  Chunk chunk;
112 
113  /* a zero-length file holds no data, and therefore no gaps */
114  if (total == 0) {
115  return 0;
116  }
117 
118  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  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  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  while ((ret < maxGaps) && (i < m_count)) {
140  next_off = (i == (m_count - 1)) ? total : m_chunks[i + 1].offset;
141  gap_start = (m_chunks[i].offset + m_chunks[i].size);
142 
143  chunk.offset = (gap_start > start) ? gap_start : start;
144  chunk.size = (next_off - chunk.offset);
145 
146  if (gap_start >= total) {
147  break;
148  } else if (start < next_off) {
149  /* Only report if gap finishes after start */
150  if (callback) {
151  callback(&chunk, opaque);
152  }
153  ++ret;
154  }
155  ++i;
156  }
157  }
158 
159  return ret;
160 }
161 
162 void CfdpChunkList::insertChunk(ChunkIdx index, const Chunk* chunk) {
163  FW_ASSERT(m_count < m_maxChunks, m_count, m_maxChunks);
164  FW_ASSERT(index <= m_count, index, m_count);
165 
166  if (m_count && (index != m_count)) {
167  memmove(&m_chunks[index + 1], &m_chunks[index], sizeof(*chunk) * (m_count - index));
168  }
169  memcpy(&m_chunks[index], chunk, sizeof(*chunk));
170 
171  ++m_count;
172 }
173 
174 void CfdpChunkList::eraseChunk(ChunkIdx index) {
175  FW_ASSERT(m_count > 0);
176  FW_ASSERT(index < m_count, index, m_count);
177 
178  /* to erase, move memory over the old one */
179  memmove(&m_chunks[index], &m_chunks[index + 1], sizeof(*m_chunks) * (m_count - 1 - index));
180  --m_count;
181 }
182 
183 void CfdpChunkList::eraseRange(ChunkIdx start, ChunkIdx end) {
184  /* Sanity check */
185  FW_ASSERT(end <= m_count, end, m_count);
186 
187  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 }
192 
193 ChunkIdx CfdpChunkList::findInsertPosition(const Chunk* chunk) {
194  ChunkIdx first = 0;
195  ChunkIdx i;
196  ChunkIdx count = m_count;
197  ChunkIdx step;
198 
199  while (count > 0) {
200  i = first;
201  step = static_cast<ChunkIdx>(count / 2);
202  i = static_cast<ChunkIdx>(i + step);
203  if (m_chunks[i].offset < chunk->offset) {
204  first = static_cast<ChunkIdx>(i + 1);
205  count = static_cast<ChunkIdx>(count - static_cast<ChunkIdx>(step + 1));
206  } else {
207  count = step;
208  }
209  }
210 
211  return first;
212 }
213 
214 bool CfdpChunkList::combineNext(ChunkIdx i, const Chunk* chunk) {
215  ChunkIdx combined_i = i;
216  bool ret = false;
217  FileSize chunk_end = chunk->offset + chunk->size;
218 
219  /* Assert no rollover, only possible as a bug */
220  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  for (; combined_i < m_count; ++combined_i) {
225  /* Advance combine index until there is a gap between end and the next offset */
226  if (chunk_end < m_chunks[combined_i].offset) {
227  break;
228  }
229  }
230 
231  /* If index advanced the range of chunks can be combined */
232  if (i != combined_i) {
233  /* End is the max of last combined chunk end or new chunk end */
234  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  m_chunks[i].size = chunk_end - chunk->offset;
238  m_chunks[i].offset = chunk->offset;
239 
240  /* Erase the rest of the combined chunks (if any) */
241  eraseRange(static_cast<ChunkIdx>(i + 1), combined_i);
242  ret = true;
243  }
244 
245  return ret;
246 }
247 
248 bool CfdpChunkList::combinePrevious(ChunkIdx i, const Chunk* chunk) {
249  Chunk* prev;
250  FileSize prev_end;
251  FileSize chunk_end;
252  bool ret = false;
253 
254  FW_ASSERT(i <= m_maxChunks, i, m_maxChunks);
255 
256  /* Only need to check if there is a previous */
257  if (i > 0) {
258  chunk_end = chunk->offset + chunk->size;
259  prev = &m_chunks[i - 1];
260  prev_end = prev->offset + prev->size;
261 
262  /* Check if start of new chunk is less than end of previous (overlaps) */
263  if (chunk->offset <= prev_end) {
264  /* When combining, use the bigger of the two endings */
265  if (prev_end < chunk_end) {
266  /* Combine with previous chunk */
267  prev->size = chunk_end - prev->offset;
268  }
269  ret = true;
270  }
271  }
272  return ret;
273 }
274 
275 void CfdpChunkList::insert(ChunkIdx i, const Chunk* chunk) {
276  ChunkIdx smallest_i;
277  Chunk* smallest_c;
278  bool next = combineNext(i, chunk);
279  bool combined;
280 
281  if (next) {
282  combined = combinePrevious(i, &m_chunks[i]);
283  if (combined) {
284  eraseChunk(i);
285  }
286  } else {
287  combined = combinePrevious(i, chunk);
288  if (!combined) {
289  if (m_count < m_maxChunks) {
290  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 }
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
void removeFromFirst(FileSize size)
Remove a specified size from the first chunk.
Definition: Chunk.cpp:87
void reset()
Reset the chunk list to empty state.
Definition: Chunk.cpp:58
FileSize size
The size of the chunk.
Definition: Chunk.hpp:51
void add(FileSize offset, FileSize size)
Add a chunk (file segment) to the list.
Definition: Chunk.cpp:63
FileSize offset
The start offset of the chunk within the file.
Definition: Chunk.hpp:50
U32 FileSize
File size and offset type.
void(*)(const Chunk *chunk, void *opaque) GapComputeCallback
Callback type for gap computation.
Definition: Chunk.hpp:75
CfdpChunkList(ChunkIdx maxChunks, Chunk *chunkMem)
Constructor - initializes chunk list with pre-allocated memory.
Definition: Chunk.cpp:51
RateGroupDivider component implementation.
const Chunk * getFirstChunk() const
Get the first chunk in the list.
Definition: Chunk.cpp:83
static FileSize CfdpChunkMax(FileSize a, FileSize b)
Selects the larger of the two passed-in offsets.
Definition: Chunk.hpp:61
#define FW_ASSERT(...)
Definition: Assert.hpp:14
U32 computeGaps(ChunkIdx maxGaps, FileSize total, FileSize start, GapComputeCallback callback, void *opaque) const
Compute gaps between chunks and invoke callback for each.
Definition: Chunk.cpp:102
Pairs an offset with a size to identify a specific piece of a file.
Definition: Chunk.hpp:49