F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
DpCompressProc.cpp
Go to the documentation of this file.
1 // ======================================================================
2 // \title DpCompressProc.cpp
3 // \author kubiak
4 // \brief cpp file for DpCompressProc component implementation class
5 // ======================================================================
6 
7 #include <cstring>
8 
10 
11 #include <Fw/Dp/DpContainer.hpp>
12 #include <Fw/Prm/ParamValid.hpp>
13 
14 namespace Svc {
15 
16 // ----------------------------------------------------------------------
17 // Component construction and destruction
18 // ----------------------------------------------------------------------
19 
20 DpCompressProc ::DpCompressProc(const char* const compName) : DpCompressProcComponentBase(compName) {}
21 
23 
24 // ----------------------------------------------------------------------
25 // Handler implementations for typed input ports
26 // ----------------------------------------------------------------------
27 
28 void DpCompressProc::serializeCompressionHeader(Fw::LinearBufferBase& serializer,
29  const FwSizeStoreType compressed_payload_size,
30  const CompressionMetadata& metadata) {
31  const FwDpIdType record_id = this->getIdBase() + RecordId::CompressionRecord;
32 
34 
35  ok = serializer.serializeFrom(record_id);
36  FW_ASSERT(ok == Fw::FW_SERIALIZE_OK, ok);
37  ok = serializer.serializeFrom(
38  static_cast<FwSizeStoreType>(compressed_payload_size + CompressionMetadata::SERIALIZED_SIZE));
39  FW_ASSERT(ok == Fw::FW_SERIALIZE_OK, ok);
40  ok = serializer.serializeFrom(metadata);
41  FW_ASSERT(ok == Fw::FW_SERIALIZE_OK, ok);
42 }
43 
44 void DpCompressProc ::procRequest_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
45  Fw::ParamValid param_valid;
46  Fw::Enabled en_compression = paramGet_ENABLE(param_valid);
47  FW_ASSERT(FW_PARAM_OK(param_valid), param_valid);
48  if (en_compression == Fw::Enabled::DISABLED) {
49  // Bypass compression
50  return;
51  }
52 
54  return;
55  }
56 
57  // Check that the buffer is large enough to hold a data product packet
58  if (fwBuffer.getSize() < Fw::DpContainer::MIN_PACKET_SIZE) {
60  return;
61  }
62 
63  Fw::DpContainer container(0, fwBuffer);
64  const Fw::SerializeStatus desStat = container.deserializeHeader();
65  if (desStat != Fw::FW_SERIALIZE_OK) {
66  // Cannot process a container with an invalid header. Give up
67  this->log_WARNING_HI_InvalidHeader(fwBuffer.getSize(), static_cast<U32>(desStat));
68  return;
69  }
70 
71  // Record sizes are stored as FwSizeStoreType. Give up on any buffer
72  // whose contents could overflow a record size field
73  if (fwBuffer.getSize() >= std::numeric_limits<FwSizeStoreType>::max()) {
74  this->log_WARNING_HI_ContainerTooLarge(container.getId(), container.getDataSize());
75  return;
76  }
77  // Consistency check: the header's data size must fit within the buffer
78  if (container.getDataSize() > fwBuffer.getSize() - Fw::DpContainer::MIN_PACKET_SIZE) {
80  Fw::DpContainer::MIN_PACKET_SIZE + container.getDataSize());
81  return;
82  }
83 
84  FwSizeType prm_chunk_size = paramGet_CHUNK_SIZE(param_valid);
85  FW_ASSERT(FW_PARAM_OK(param_valid), param_valid);
86 
87  if (prm_chunk_size > container.getDataSize()) {
88  prm_chunk_size = container.getDataSize();
89  }
90  const FwSizeType max_chunk_size = prm_chunk_size;
91  FW_ASSERT(max_chunk_size <= std::numeric_limits<FwSizeStoreType>::max(),
92  static_cast<FwAssertArgType>(max_chunk_size));
93  if (max_chunk_size <= 0) {
94  // Chunk size is invalid. Give up
95  return;
96  }
97 
98  Fw::Buffer data_buffer(fwBuffer.getData() + Fw::DpContainer::DATA_OFFSET,
100  FW_ASSERT(data_buffer.getSize() > 0, static_cast<FwAssertArgType>(data_buffer.getSize()));
101 
102  auto data_deser = data_buffer.getDeserializer();
103  auto data_reser = data_buffer.getSerializer();
104 
105  // Compression record serialized size
106  const FwSizeType compression_header_size =
108 
109  // DpCompressProc processing state machine.
110  // Tracks the processing steps that need to occur for
111  // each chunk, whether is is compressed or not
112  enum {
113  // Initial state. First processing state will
114  // either be PRE_COMMIT or LAST_COMPRESSED depending
115  // on the compression of the first chunk.
116  INIT,
117 
118  // Have not committed to returning a compressed
119  // data product. Stay in this state until at least
120  // one chunk is found to be compressible
121  PRE_COMMIT,
122 
123  // The last chunk was compressed.
124  // The next chunk will need to have its own
125  // header prepended
126  LAST_COMPRESSED,
127 
128  // The last chunk was uncompressed.
129  // If more uncompressed chunks are found then the
130  // size of the last uncompressed segment needs to
131  // grow to accommodate this segment
132  LAST_UNCOMPRESSED
133  } state;
134 
135  state = INIT;
136 
137  FwSizeType uncompressed_size = 0;
138  U8* uncompressed_head = nullptr;
140 
141  while (data_deser.getDeserializeSizeLeft() > 0) {
142  // Chunk size for this iteration
143  const FwSizeType chunk_size_tmp = (data_deser.getDeserializeSizeLeft() < max_chunk_size)
144  ? static_cast<FwSizeType>(data_deser.getDeserializeSizeLeft())
145  : max_chunk_size;
146 
147  // Ensure that FwSizeStoreType is large enough to hold the chunk_size
148  FW_ASSERT(chunk_size_tmp > 0, static_cast<FwAssertArgType>(chunk_size_tmp));
149  FW_ASSERT(chunk_size_tmp <= std::numeric_limits<FwSizeStoreType>::max(),
150  static_cast<FwAssertArgType>(chunk_size_tmp));
151  const FwSizeStoreType chunk_size = static_cast<FwSizeStoreType>(chunk_size_tmp);
152  // a const U8 * and I need to mutable U8*
153  U8* data_offset = data_buffer.getData() + (data_buffer.getSize() - data_deser.getDeserializeSizeLeft());
154 
155  Fw::Buffer compression_buffer(data_offset, chunk_size);
156 
157  // Jump the deserializer to mark the data as read
158  ser_stat = data_deser.deserializeSkip(chunk_size);
159  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
160 
161  CompressionAlgorithm alg = CompressionAlgorithm::UNCOMPRESSED;
162  FwSizeType min_compression = 0;
163  FwSizeType compression_offset = 0;
164 
165  // Only attempt compression on chunks larger than
166  // 4 times the header size. Sizes below 3 times the header
167  // size could trigger negative sizes in the min_compression
168  // calculation
169  if (chunk_size > 4 * compression_header_size) {
170  switch (state) {
171  case INIT:
172  // Need room for two headers.
173  // 1. This compressed chunk
174  // 2. A potential uncompressed chunk that follows
175  // This is assuming that the initial chunk can be compressed
176  min_compression = chunk_size - 2 * compression_header_size;
177 
178  // Compression needs to occur in the buffer with enough
179  // space for the this header
180  compression_offset = compression_header_size;
181  break;
182  case PRE_COMMIT:
183  // Need room for three headers.
184  // 1. The initial uncompressed chunk
185  // 2. This compressed chunk
186  // 3. A potential uncompressed chunk that follows
187  min_compression = chunk_size - 3 * compression_header_size;
188 
189  // Compression needs to occur in the buffer with enough
190  // space for the this header AND the initial uncompressed header
191  compression_offset = 2 * compression_header_size;
192  break;
193  case LAST_UNCOMPRESSED:
194  case LAST_COMPRESSED:
195  // Need room for two headers.
196  // 1. The next compressed chunk
197  // 2. A potential uncompressed chunk
198  // Can take advantage of compression that has occurred
199  // before this point
200  // Note: Could keep track of the number of available spare bytes
201  // in the buffer, but that adds a complexity for very little benefit
202  min_compression = chunk_size - (2 * compression_header_size);
203  // Compression needs to occur in the buffer with enough
204  // space for the this header
205  compression_offset = compression_header_size;
206  break;
207  default:
208  FW_ASSERT(false, state);
209  break;
210  }
211 
212  if (min_compression > chunk_size - compression_offset) {
213  min_compression = chunk_size - compression_offset;
214  }
215 
216  FW_ASSERT(min_compression <= chunk_size, static_cast<FwAssertArgType>(min_compression),
217  static_cast<FwAssertArgType>(chunk_size));
218 
219  FW_ASSERT(compression_offset <= chunk_size, static_cast<FwAssertArgType>(compression_offset),
220  static_cast<FwAssertArgType>(chunk_size));
221 
222  alg = compressChunk_out(0, compression_buffer, min_compression, compression_offset);
223 
225  // The compressor writes up to min_compression bytes of compressed
226  // data after write_offset bytes of reserved space
227  FW_ASSERT(compression_buffer.getSize() <= min_compression + compression_offset,
228  static_cast<FwAssertArgType>(compression_buffer.getSize()),
229  static_cast<FwAssertArgType>(min_compression),
230  static_cast<FwAssertArgType>(compression_offset));
231  }
232  }
233 
235  // Data was compressed
236 
237  U8* const comp_data_ptr = compression_buffer.getData() + compression_offset;
238 
239  const FwSizeType compressed_size_tmp =
240  static_cast<FwSizeType>(compression_buffer.getSize()) - static_cast<FwSizeType>(compression_offset);
241  FW_ASSERT(compressed_size_tmp <= std::numeric_limits<FwSizeStoreType>::max());
242  FW_ASSERT(compressed_size_tmp < chunk_size);
243  const FwSizeStoreType compressed_size = static_cast<FwSizeStoreType>(compressed_size_tmp);
244 
245  // If the first chunk is compressible treat the state as
246  // if it was LAST_COMPRESSED. No need to perform the special
247  // move and header serialization in PRE_COMMIT
248  if (state == INIT) {
249  state = LAST_COMPRESSED;
250  }
251 
252  const FwSizeType deser_loc = data_deser.getSize() - data_deser.getDeserializeSizeLeft();
253  switch (state) {
254  case PRE_COMMIT:
255  // Case A.
256  // 1. Memmove uncompressed data so far by the size of compressed header
257  // 2. Prepend a header to the uncompressed data
258  // 3. Write a compressed header to the compressed data
259  // 4. Serialize compressed data
260 
261  // Assert that the serialization operations in Case A will stay within deser_loc bounds
262  FW_ASSERT(deser_loc >= (uncompressed_size + 2 * compression_header_size + compressed_size),
263  static_cast<FwAssertArgType>(deser_loc), static_cast<FwAssertArgType>(uncompressed_size),
264  static_cast<FwAssertArgType>(compressed_size));
265  (void)std::memmove(data_buffer.getData() + compression_header_size, data_buffer.getData(),
266  uncompressed_size);
267 
268  // Serialize the header bytes to the front of the data
269  serializeCompressionHeader(data_reser, static_cast<FwSizeStoreType>(uncompressed_size),
270  CompressionMetadata(CompressionAlgorithm::UNCOMPRESSED));
271 
272  // Move the serializer past the uncompressed chunk manually
273  ser_stat = data_reser.serializeSkip(uncompressed_size);
274  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
275 
276  serializeCompressionHeader(data_reser, compressed_size, CompressionMetadata(alg));
277 
278  ser_stat = data_reser.serializeFrom(comp_data_ptr, compressed_size, Fw::Serialization::OMIT_LENGTH);
279  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
280  break;
281  case LAST_COMPRESSED:
282  // Case B
283  // 1. Write header at data_reser location
284  // 2. Serialize compressed data
285  serializeCompressionHeader(data_reser, compressed_size, CompressionMetadata(alg));
286 
287  ser_stat = data_reser.serializeFrom(comp_data_ptr, compressed_size, Fw::Serialization::OMIT_LENGTH);
288  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
289  break;
290  case LAST_UNCOMPRESSED:
291  // Case E
292  // 1. Write header for uncompressed data.
293  // data_reser has been kept at this location
294  // 2. Serialize uncompressed data
295  // 3. Write header for compressed data at data_reser location
296  // 4. Serialize compressed data
297 
298  // Assert that the serialization operations in Case E will stay within deser_loc bounds
299  FW_ASSERT(
300  deser_loc >=
301  (data_reser.getSize() + uncompressed_size + 2 * compression_header_size + compressed_size),
302  static_cast<FwAssertArgType>(deser_loc), static_cast<FwAssertArgType>(data_reser.getSize()),
303  static_cast<FwAssertArgType>(uncompressed_size), static_cast<FwAssertArgType>(compressed_size));
304  serializeCompressionHeader(data_reser, static_cast<FwSizeStoreType>(uncompressed_size),
305  CompressionMetadata(CompressionAlgorithm::UNCOMPRESSED));
306 
307  FW_ASSERT(uncompressed_head != nullptr);
308  ser_stat =
309  data_reser.serializeFrom(uncompressed_head, uncompressed_size, Fw::Serialization::OMIT_LENGTH);
310  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
311 
312  serializeCompressionHeader(data_reser, compressed_size, CompressionMetadata(alg));
313 
314  ser_stat = data_reser.serializeFrom(comp_data_ptr, compressed_size, Fw::Serialization::OMIT_LENGTH);
315  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
316  break;
317  default:
318  FW_ASSERT(false, state);
319  break;
320  }
321 
322  // Last chunk was compressed. Remove history of uncompressed data
323  uncompressed_size = 0;
324  uncompressed_head = nullptr;
325  state = LAST_COMPRESSED;
326  } else {
327  // Data was uncompressed
328 
329  // If the first chunk is not compressible treat the state as
330  // if it was PRE_COMMIT
331  if (state == INIT) {
332  state = PRE_COMMIT;
333  }
334 
335  switch (state) {
336  case PRE_COMMIT:
337  // No work.
338  // Keep looking for a compressible chunk in order to commit
339  // to a compressed structure
340  state = PRE_COMMIT;
341  break;
342  case LAST_COMPRESSED:
343  // Case C - First incompressible chunk
344  //
345  // 1. Mark the location of the start of incompressible chunks
346  uncompressed_head = compression_buffer.getData();
347 
348  state = LAST_UNCOMPRESSED;
349  break;
350  case LAST_UNCOMPRESSED:
351  // Case D - Continued sequence of incompressible chunks
352  // No work. Keep increasing track of uncompressed_size
353  state = LAST_UNCOMPRESSED;
354  break;
355  default:
356  FW_ASSERT(false, state);
357  break;
358  }
359  uncompressed_size += chunk_size;
360  }
361 
362  // Confirm that the serialized location has not jumped ahead of the deserialize location
363  const FwSizeType ser_loc = data_reser.getSize();
364  const FwSizeType deser_loc = static_cast<uintptr_t>(data_deser.getBuffAddrLeft() - data_buffer.getData());
365  FW_ASSERT(ser_loc <= deser_loc, static_cast<FwAssertArgType>(ser_loc), static_cast<FwAssertArgType>(deser_loc));
366  }
367 
368  switch (state) {
369  case PRE_COMMIT:
370  // Never compressed a chunk. Return the buffer
371  // unchanged
372  log_ACTIVITY_LO_DidNotCompress(container.getId(), container.getDataSize());
373  break;
374  case LAST_UNCOMPRESSED:
375  // Need to serialize the last bit of uncompressed data
376  // 1. Write header for uncompressed data.
377  // data_reser has been kept at this location
378  // 2. Serialize uncompressed data
379  serializeCompressionHeader(data_reser, static_cast<FwSizeStoreType>(uncompressed_size),
380  CompressionMetadata(CompressionAlgorithm::UNCOMPRESSED));
381 
382  FW_ASSERT(uncompressed_head != nullptr);
383  ser_stat = data_reser.serializeFrom(uncompressed_head, uncompressed_size, Fw::Serialization::OMIT_LENGTH);
384  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
385 
386  /* FALLTHRU */
387  case LAST_COMPRESSED:
388  // Update buffer and header size to be consistent
389  // with the compression achieved
390 
391  {
392  FwSizeType comp_data_size = data_reser.getSize();
393  log_DIAGNOSTIC_CompressionComplete(container.getId(), container.getDataSize(), comp_data_size);
394 
395  container.setDataSize(comp_data_size);
396  container.serializeHeader();
397  }
398  break;
399  default:
400  FW_ASSERT(false, state);
401  }
402 }
403 
404 } // namespace Svc
Serialization/Deserialization operation was successful.
A data product Container.
Definition: DpContainer.hpp:26
void log_WARNING_HI_BufferTooSmallForPacket(FwSizeType buffer_size, FwSizeType min_size)
#define FW_PARAM_OK(paramValid)
Definition: ParamValid.hpp:22
PlatformSizeType FwSizeType
void log_WARNING_HI_InvalidHeader(FwSizeType buffer_size, U32 error_code)
static constexpr FwSizeType MIN_PACKET_SIZE
Definition: DpContainer.hpp:65
U8 * getData() const
Definition: Buffer.cpp:82
SerializeStatus serializeFrom(U8 val, Endianness mode=Endianness::BIG) override
Serialize an 8-bit unsigned integer value.
bool isConnected_compressChunk_OutputPort(FwIndexType portNum) const
SerializeStatus
forward declaration for string
void log_ACTIVITY_LO_DidNotCompress(FwDpIdType dp_id, FwSizeType data_size)
Log event DidNotCompress.
U16 FwSizeStoreType
The type used to serialize a size value.
Auto-generated base for DpCompressProc component.
Omit length from serialization.
~DpCompressProc()
Destroy DpCompressProc object.
Svc::CompressionAlgorithm compressChunk_out(FwIndexType portNum, Fw::Buffer &buffer, FwSizeType min_compression, FwSizeType write_offset) const
Invoke output port compressChunk.
void log_DIAGNOSTIC_CompressionComplete(FwDpIdType dp_id, FwSizeType initial_size, FwSizeType final_size) const
Log event CompressionComplete.
Enabled and disabled states.
static constexpr FwSizeType DATA_OFFSET
The data offset.
Definition: DpContainer.hpp:61
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
FwSizeType getSize() const
Definition: Buffer.cpp:90
FwIdType FwDpIdType
The type of a data product identifier.
FwSizeStoreType paramGet_CHUNK_SIZE(Fw::ParamValid &valid)
DpCompressProc(const char *const compName)
Construct DpCompressProc object.
PlatformIndexType FwIndexType
RateGroupDivider component implementation.
Enum representing parameter validity.
void log_WARNING_HI_ContainerTooLarge(FwDpIdType dp_id, FwSizeType data_size)
Fw::Enabled paramGet_ENABLE(Fw::ParamValid &valid)
#define FW_ASSERT(...)
Definition: Assert.hpp:14
Disabled state.
PlatformAssertArgType FwAssertArgType
The type of arguments to assert functions.