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 
13 namespace Svc {
14 
15 // ----------------------------------------------------------------------
16 // Component construction and destruction
17 // ----------------------------------------------------------------------
18 
19 DpCompressProc ::DpCompressProc(const char* const compName) : DpCompressProcComponentBase(compName) {}
20 
22 
23 // ----------------------------------------------------------------------
24 // Handler implementations for typed input ports
25 // ----------------------------------------------------------------------
26 
27 void DpCompressProc::serializeCompressionHeader(Fw::LinearBufferBase& serializer,
28  const FwSizeStoreType compressed_payload_size,
29  const CompressionMetadata& metadata) {
30  const FwDpIdType record_id = this->getIdBase() + RecordId::CompressionRecord;
31 
33 
34  ok = serializer.serializeFrom(record_id);
35  FW_ASSERT(ok == Fw::FW_SERIALIZE_OK, ok);
36  ok = serializer.serializeFrom(
37  static_cast<FwSizeStoreType>(compressed_payload_size + CompressionMetadata::SERIALIZED_SIZE));
38  FW_ASSERT(ok == Fw::FW_SERIALIZE_OK, ok);
39  ok = serializer.serializeFrom(metadata);
40  FW_ASSERT(ok == Fw::FW_SERIALIZE_OK, ok);
41 }
42 
43 void DpCompressProc ::procRequest_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
44  Fw::ParamValid param_valid;
45  Fw::Enabled en_compression = paramGet_ENABLE(param_valid);
46  FW_ASSERT((param_valid == Fw::ParamValid::DEFAULT) || (param_valid == Fw::ParamValid::VALID), param_valid);
47  if (en_compression == Fw::Enabled::DISABLED) {
48  // Bypass compression
49  return;
50  }
51 
53  return;
54  }
55 
56  Fw::DpContainer container(0, fwBuffer);
57  container.deserializeHeader();
58 
59  FwSizeType prm_chunk_size = paramGet_CHUNK_SIZE(param_valid);
60  FW_ASSERT((param_valid == Fw::ParamValid::DEFAULT) || (param_valid == Fw::ParamValid::VALID), param_valid);
61 
62  if (prm_chunk_size > container.getDataSize()) {
63  prm_chunk_size = container.getDataSize();
64  }
65  const FwSizeType max_chunk_size = prm_chunk_size;
66  FW_ASSERT(max_chunk_size <= std::numeric_limits<FwSizeStoreType>::max(),
67  static_cast<FwAssertArgType>(max_chunk_size));
68  if (max_chunk_size <= 0) {
69  // Chunk size is invalid. Give up
70  return;
71  }
72 
73  Fw::Buffer data_buffer(fwBuffer.getData() + Fw::DpContainer::DATA_OFFSET,
75  FW_ASSERT(data_buffer.getSize() > 0, static_cast<FwAssertArgType>(data_buffer.getSize()));
76 
77  auto data_deser = data_buffer.getDeserializer();
78  auto data_reser = data_buffer.getSerializer();
79 
80  // Compression record serialized size
81  const FwSizeType compression_header_size =
83 
84  // DpCompressProc processing state machine.
85  // Tracks the processing steps that need to occur for
86  // each chunk, whether is is compressed or not
87  enum {
88  // Initial state. First processing state will
89  // either be PRE_COMMIT or LAST_COMPRESSED depending
90  // on the compression of the first chunk.
91  INIT,
92 
93  // Have not committed to returning a compressed
94  // data product. Stay in this state until at least
95  // one chunk is found to be compressible
96  PRE_COMMIT,
97 
98  // The last chunk was compressed.
99  // The next chunk will need to have its own
100  // header prepended
101  LAST_COMPRESSED,
102 
103  // The last chunk was uncompressed.
104  // If more uncompressed chunks are found then the
105  // size of the last uncompressed segment needs to
106  // grow to accommodate this segment
107  LAST_UNCOMPRESSED
108  } state;
109 
110  state = INIT;
111 
112  FwSizeStoreType uncompressed_size = 0;
113  U8* uncompressed_head = nullptr;
115 
116  while (data_deser.getDeserializeSizeLeft() > 0) {
117  // Chunk size for this iteration
118  const FwSizeType chunk_size_tmp = (data_deser.getDeserializeSizeLeft() < max_chunk_size)
119  ? static_cast<FwSizeType>(data_deser.getDeserializeSizeLeft())
120  : max_chunk_size;
121 
122  // Ensure that FwSizeStoreType is large enough to hold the chunk_size
123  FW_ASSERT(chunk_size_tmp > 0, static_cast<FwAssertArgType>(chunk_size_tmp));
124  FW_ASSERT(chunk_size_tmp <= std::numeric_limits<FwSizeStoreType>::max(),
125  static_cast<FwAssertArgType>(chunk_size_tmp));
126  const FwSizeStoreType chunk_size = static_cast<FwSizeStoreType>(chunk_size_tmp);
127  // a const U8 * and I need to mutable U8*
128  U8* data_offset = data_buffer.getData() + (data_buffer.getSize() - data_deser.getDeserializeSizeLeft());
129 
130  Fw::Buffer compression_buffer(data_offset, chunk_size);
131 
132  // Jump the deserializer to mark the data as read
133  ser_stat = data_deser.deserializeSkip(chunk_size);
134  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
135 
136  CompressionAlgorithm alg = CompressionAlgorithm::UNCOMPRESSED;
137  FwSizeType min_compression = 0;
138  FwSizeType compression_offset = 0;
139 
140  // Only attempt compression on chunks larger than
141  // 4 times the header size. Sizes below 3 times the header
142  // size could trigger negative sizes in the min_compression
143  // calculation
144  if (chunk_size > 4 * compression_header_size) {
145  switch (state) {
146  case INIT:
147  // Need room for two headers.
148  // 1. This compressed chunk
149  // 2. A potential uncompressed chunk that follows
150  // This is assuming that the initial chunk can be compressed
151  min_compression = chunk_size - 2 * compression_header_size;
152 
153  // Compression needs to occur in the buffer with enough
154  // space for the this header
155  compression_offset = compression_header_size;
156  break;
157  case PRE_COMMIT:
158  // Need room for three headers.
159  // 1. The initial uncompressed chunk
160  // 2. This compressed chunk
161  // 3. A potential uncompressed chunk that follows
162  min_compression = chunk_size - 3 * compression_header_size;
163 
164  // Compression needs to occur in the buffer with enough
165  // space for the this header AND the initial uncompressed header
166  compression_offset = 2 * compression_header_size;
167  break;
168  case LAST_UNCOMPRESSED:
169  case LAST_COMPRESSED:
170  // Need room for two headers.
171  // 1. The next compressed chunk
172  // 2. A potential uncompressed chunk
173  // Can take advantage of compression that has occurred
174  // before this point
175  // Note: Could keep track of the number of available spare bytes
176  // in the buffer, but that adds a complexity for very little benefit
177  min_compression = chunk_size - (2 * compression_header_size);
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  default:
183  FW_ASSERT(false, state);
184  break;
185  }
186 
187  if (min_compression > chunk_size - compression_offset) {
188  min_compression = chunk_size - compression_offset;
189  }
190 
191  FW_ASSERT(min_compression <= chunk_size, static_cast<FwAssertArgType>(min_compression),
192  static_cast<FwAssertArgType>(chunk_size));
193 
194  FW_ASSERT(compression_offset <= chunk_size, static_cast<FwAssertArgType>(compression_offset),
195  static_cast<FwAssertArgType>(chunk_size));
196 
197  alg = compressChunk_out(0, compression_buffer, min_compression, compression_offset);
198 
200  FW_ASSERT(compression_buffer.getSize() <= min_compression);
201  }
202  }
203 
205  // Data was compressed
206 
207  U8* const comp_data_ptr = compression_buffer.getData() + compression_offset;
208 
209  const FwSizeType compressed_size_tmp =
210  static_cast<FwSizeType>(compression_buffer.getSize()) - static_cast<FwSizeType>(compression_offset);
211  FW_ASSERT(compressed_size_tmp <= std::numeric_limits<FwSizeStoreType>::max());
212  FW_ASSERT(compressed_size_tmp < chunk_size);
213  const FwSizeStoreType compressed_size = static_cast<FwSizeStoreType>(compressed_size_tmp);
214 
215  // If the first chunk is compressible treat the state as
216  // if it was LAST_COMPRESSED. No need to perform the special
217  // move and header serialization in PRE_COMMIT
218  if (state == INIT) {
219  state = LAST_COMPRESSED;
220  }
221 
222  const FwSizeType deser_loc = data_deser.getSize() - data_deser.getDeserializeSizeLeft();
223  switch (state) {
224  case PRE_COMMIT:
225  // Case A.
226  // 1. Memmove uncompressed data so far by the size of compressed header
227  // 2. Prepend a header to the uncompressed data
228  // 3. Write a compressed header to the compressed data
229  // 4. Serialize compressed data
230 
231  // Assert the next few serialization operations will stay within the
232  // data that has been read so far
233  FW_ASSERT(deser_loc >=
234  (uncompressed_size +
235  2 * static_cast<FwSizeType>(CompressionMetadata::SERIALIZED_SIZE) + compressed_size),
236  static_cast<FwAssertArgType>(deser_loc), uncompressed_size,
237  CompressionMetadata::SERIALIZED_SIZE, compressed_size);
238  std::memmove(data_buffer.getData() + compression_header_size, data_buffer.getData(),
239  uncompressed_size);
240 
241  // Serialize the header bytes to the front of the data
242  serializeCompressionHeader(data_reser, uncompressed_size,
243  CompressionMetadata(CompressionAlgorithm::UNCOMPRESSED));
244 
245  // Move the serializer past the uncompressed chunk manually
246  ser_stat = data_reser.serializeSkip(uncompressed_size);
247  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
248 
249  serializeCompressionHeader(data_reser, compressed_size, CompressionMetadata(alg));
250 
251  ser_stat = data_reser.serializeFrom(comp_data_ptr, compressed_size, Fw::Serialization::OMIT_LENGTH);
252  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
253  break;
254  case LAST_COMPRESSED:
255  // Case B
256  // 1. Write header at data_reser location
257  // 2. Serialize compressed data
258  serializeCompressionHeader(data_reser, compressed_size, CompressionMetadata(alg));
259 
260  ser_stat = data_reser.serializeFrom(comp_data_ptr, compressed_size, Fw::Serialization::OMIT_LENGTH);
261  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
262  break;
263  case LAST_UNCOMPRESSED:
264  // Case E
265  // 1. Write header for uncompressed data.
266  // data_reser has been kept at this location
267  // 2. Serialize uncompressed data
268  // 3. Write header for compressed data at data_reser location
269  // 4. Serialize compressed data
270  serializeCompressionHeader(data_reser, uncompressed_size,
271  CompressionMetadata(CompressionAlgorithm::UNCOMPRESSED));
272 
273  FW_ASSERT(uncompressed_head != nullptr);
274  ser_stat =
275  data_reser.serializeFrom(uncompressed_head, uncompressed_size, Fw::Serialization::OMIT_LENGTH);
276  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
277 
278  serializeCompressionHeader(data_reser, compressed_size, CompressionMetadata(alg));
279 
280  ser_stat = data_reser.serializeFrom(comp_data_ptr, compressed_size, Fw::Serialization::OMIT_LENGTH);
281  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
282  break;
283  default:
284  FW_ASSERT(false, state);
285  break;
286  }
287 
288  // Last chunk was compressed. Remove history of uncompressed data
289  uncompressed_size = 0;
290  uncompressed_head = nullptr;
291  state = LAST_COMPRESSED;
292  } else {
293  // Data was uncompressed
294 
295  // If the first chunk is not compressible treat the state as
296  // if it was PRE_COMMIT
297  if (state == INIT) {
298  state = PRE_COMMIT;
299  }
300 
301  switch (state) {
302  case PRE_COMMIT:
303  // No work.
304  // Keep looking for a compressible chunk in order to commit
305  // to a compressed structure
306  state = PRE_COMMIT;
307  break;
308  case LAST_COMPRESSED:
309  // Case C - First incompressible chunk
310  //
311  // 1. Mark the location of the start of incompressible chunks
312  uncompressed_head = compression_buffer.getData();
313 
314  state = LAST_UNCOMPRESSED;
315  break;
316  case LAST_UNCOMPRESSED:
317  // Case D - Continued sequence of incompressible chunks
318  // No work. Keep increasing track of uncompressed_size
319  state = LAST_UNCOMPRESSED;
320  break;
321  default:
322  FW_ASSERT(false, state);
323  break;
324  }
325  uncompressed_size = static_cast<FwSizeStoreType>(uncompressed_size + chunk_size);
326  }
327 
328  // Confirm that the serialized location has not jumped ahead of the deserialize location
329  const FwSizeType ser_loc = data_reser.getSize();
330  const FwSizeType deser_loc = static_cast<uintptr_t>(data_deser.getBuffAddrLeft() - data_buffer.getData());
331  FW_ASSERT(ser_loc <= deser_loc, static_cast<FwAssertArgType>(ser_loc), static_cast<FwAssertArgType>(deser_loc));
332  }
333 
334  switch (state) {
335  case PRE_COMMIT:
336  // Never compressed a chunk. Return the buffer
337  // unchanged
338  log_ACTIVITY_LO_DidNotCompress(container.getId(), container.getDataSize());
339  break;
340  case LAST_UNCOMPRESSED:
341  // Need to serialize the last bit of uncompressed data
342  // 1. Write header for uncompressed data.
343  // data_reser has been kept at this location
344  // 2. Serialize uncompressed data
345  serializeCompressionHeader(data_reser, uncompressed_size,
346  CompressionMetadata(CompressionAlgorithm::UNCOMPRESSED));
347 
348  FW_ASSERT(uncompressed_head != nullptr);
349  ser_stat = data_reser.serializeFrom(uncompressed_head, uncompressed_size, Fw::Serialization::OMIT_LENGTH);
350  FW_ASSERT(ser_stat == Fw::FW_SERIALIZE_OK, ser_stat);
351 
352  /* FALLTHRU */
353  case LAST_COMPRESSED:
354  // Update buffer and header size to be consistent
355  // with the compression achieved
356 
357  {
358  FwSizeType comp_data_size = data_reser.getSize();
359  log_DIAGNOSTIC_CompressionComplete(container.getId(), container.getDataSize(), comp_data_size);
360 
361  container.setDataSize(comp_data_size);
362  container.serializeHeader();
363  }
364  break;
365  default:
366  FW_ASSERT(false, state);
367  }
368 }
369 
370 } // namespace Svc
Serialization/Deserialization operation was successful.
A data product Container.
Definition: DpContainer.hpp:26
SerializeStatus serializeFrom(U8 val, Endianness mode=Endianness::BIG) override
Serialize an 8-bit unsigned integer value.
PlatformSizeType FwSizeType
static constexpr FwSizeType MIN_PACKET_SIZE
Definition: DpContainer.hpp:65
U8 * getData() const
Definition: Buffer.cpp:56
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:60
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.
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.