F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
AosDeframer.cpp
Go to the documentation of this file.
1 // ======================================================================
2 // \title AosDeframer.cpp
3 // \author Will MacCormack
4 // \brief cpp file for AosDeframer component implementation class
5 //
6 // Deframer for the AOS Space Data Link Protocol per CCSDS 732.0-B-5.
7 // Supports M_PDU data field service with:
8 // - Frame Error Control Field (FECF) validation (Section 4.1.6)
9 // - Space Packet Protocol (SPP) extraction (CCSDS 133.0-B-2)
10 // - Encapsulation Packet Protocol (EPP) extraction (CCSDS 133.1-B-3)
11 // ======================================================================
12 
14 #include <cstring>
20 
21 namespace Svc {
22 namespace Ccsds {
23 
24 // ----------------------------------------------------------------------
25 // Component construction and destruction
26 // ----------------------------------------------------------------------
27 
28 AosDeframer::AosDeframer(const char* const compName)
29  : AosDeframerComponentBase(compName),
30  m_fixedFrameSize(ComCfg::AosMaxFrameFixedSize),
31  m_fecfEnabled(true),
32  m_spacecraftId(ComCfg::SpacecraftId),
33  m_crcErrorCount(0) {
34  // Initialize VC struct
35  for (U8 vcInd = 0; vcInd < AosDeframer_NumVcs; vcInd++) {
36  m_vcs[vcInd].vcStructIndex = vcInd;
37  }
38 }
39 
41 
42 void AosDeframer::configure(U32 fixedFrameSize, bool frameErrorControlField, U16 spacecraftId, U8 vcId, U8 pvnMask) {
43  // Validate frame size is within bounds
44  FW_ASSERT(fixedFrameSize <= ComCfg::AosMaxFrameFixedSize, static_cast<FwAssertArgType>(fixedFrameSize),
45  static_cast<FwAssertArgType>(ComCfg::AosMaxFrameFixedSize));
46 
47  // Frame must be large enough for header + M_PDU header + optional trailer
49  (frameErrorControlField ? AOSTrailer::SERIALIZED_SIZE : 0);
50  FW_ASSERT(fixedFrameSize > minSize, static_cast<FwAssertArgType>(fixedFrameSize),
51  static_cast<FwAssertArgType>(minSize));
52 
53  // Spacecraft ID is 10 bits (per CCSDS 732.0-B-5 Section 4.1.2.2)
54  FW_ASSERT((spacecraftId & 0xFC00) == 0, static_cast<FwAssertArgType>(spacecraftId));
55 
56  // Virtual Channel ID is 6 bits (per CCSDS 732.0-B-5 Section 4.1.2.3)
57  FW_ASSERT((vcId & 0xC0) == 0, static_cast<FwAssertArgType>(vcId));
58 
59  // pvnMask must only contain valid PVN bits and at least one must be set
60  FW_ASSERT((pvnMask & PvnBitfield::VALID_MASK) != 0, static_cast<FwAssertArgType>(pvnMask));
61  FW_ASSERT((pvnMask & ~PvnBitfield::VALID_MASK) == 0, static_cast<FwAssertArgType>(pvnMask));
62 
63  // Spanning packet reassembly requires dynamic backing via allocator ports
66 
67  m_fixedFrameSize = fixedFrameSize;
68  m_fecfEnabled = frameErrorControlField;
69  m_spacecraftId = spacecraftId;
70 
71  // Zero out FECF error counter on (re)configure
72  m_crcErrorCount = 0;
73 
74  // Populate the (single) VC struct
75  m_vcs[0].virtualChannelId = vcId;
76  m_vcs[0].pvnMask = pvnMask;
77 
78  // Clear out all VC stats
79  for (U8 vcInd = 0; vcInd < AosDeframer_NumVcs; vcInd++) {
80  m_vcs[vcInd].framesProcessed = 0;
81  m_vcs[vcInd].packetsExtracted = 0;
82  m_vcs[vcInd].vcFrameCount = 0;
83 
84  // Clear out the spanningPacket
85  this->abandonSpanningPacket(m_vcs[vcInd]);
86  }
87 }
88 
89 // ----------------------------------------------------------------------
90 // Handler implementations for user-defined typed input ports
91 // ----------------------------------------------------------------------
92 
93 void AosDeframer::dataIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context) {
94  // Per CCSDS 732.0-B-5, AOS frames are fixed-size
95  // Verify we have received a complete frame
96  FW_ASSERT(m_fixedFrameSize > 0, static_cast<FwAssertArgType>(m_fixedFrameSize));
97 
98  if (data.getSize() < m_fixedFrameSize) {
99  this->log_WARNING_HI_InvalidFrameLength(data.getSize(), m_fixedFrameSize);
100  this->notifyErrorIfConnected(Ccsds::FrameError::AOS_INVALID_LENGTH);
101  this->dataReturnOut_out(0, data, context);
102  return;
103  }
104 
105  // Validate FECF if enabled (Section 4.1.6)
106  // FrameDetector + FrameAccumulator or Lower Protocol Layer should enforce whole AOS Frames
107  if (m_fecfEnabled && !this->validateFecf(data)) {
108  this->dataReturnOut_out(0, data, context);
109  return;
110  }
111 
112  // Create a mutable context for extracted packet info
113  ComCfg::FrameContext packetContext = context;
114  // Start null, and is set by the parse step
115  AosDeframerVc* vc;
116 
117  // Parse and validate the AOS Primary Header (Section 4.1.2)
118  // Note: parseAndValidateHeader handles warning events and errorNotify for header failures.
119  if ((vc = this->parseAndValidateHeader(data, packetContext)) == nullptr) {
120  this->dataReturnOut_out(0, data, context);
121  return;
122  }
123 
124  // Set the default context only if we haven't for this packet already
125  // Otherwise our PVN tracker gets overwritten
126  if (!vc->spanningPacket.buffer.isValid()) {
127  vc->spanningPacket.context = packetContext;
128  }
129 
130  // Extract packets from the M_PDU data zone
131  this->extractPackets(*vc, data);
132 
133  // Return the frame buffer
134  this->dataReturnOut_out(0, data, context);
135 
136  // Update telemetry
137  this->tlmWrite_FramesProcessed(++vc->framesProcessed);
138 }
139 
140 void AosDeframer::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer, const ComCfg::FrameContext& context) {
141  // Deallocate this dynamically allocated packet
142  this->deallocate_out(0, fwBuffer);
143 }
144 
145 // ----------------------------------------------------------------------
146 // Private helper methods
147 // ----------------------------------------------------------------------
148 
149 void AosDeframer::notifyErrorIfConnected(Ccsds::FrameError error) {
150  if (this->isConnected_errorNotify_OutputPort(0)) {
151  this->errorNotify_out(0, error);
152  }
153 }
154 
155 void AosDeframer::abandonSpanningPacket(AosDeframerVc& vc) {
156  if (vc.spanningPacket.buffer.isValid()) {
157  this->log_WARNING_HI_SpanningPacketAbandoned(vc.virtualChannelId, vc.spanningPacket.context.get_pvn(),
158  vc.spanningPacket.bytesReceived,
159  vc.spanningPacket.buffer.getSize());
160  this->deallocate_out(0, vc.spanningPacket.buffer);
161  }
162  vc.spanningPacket.buffer = Fw::Buffer();
163  vc.spanningPacket.bytesReceived = 0;
164  vc.spanningPacket.context.set_pvn(ComCfg::Pvn::INVALID_UNINITIALIZED);
165 }
166 
167 AosDeframer::AosDeframerVc* AosDeframer::getVcStruct(const U8 vcId) {
168  for (U8 vcInd = 0; vcInd < AosDeframer_NumVcs; vcInd++) {
169  if (m_vcs[vcInd].virtualChannelId == vcId) {
170  return &m_vcs[vcInd];
171  }
172  }
173 
174  return nullptr;
175 }
176 
177 AosDeframer::AosDeframerVc* AosDeframer::parseAndValidateHeader(Fw::Buffer& data, ComCfg::FrameContext& context) {
178  // Deserialize the AOS Primary Header (per CCSDS 732.0-B-5 Section 4.1.2)
179  AOSHeader header;
180  Fw::SerializeStatus status = data.getDeserializer().deserializeTo(header);
181  // We already checked that a header fits into fixedFrameSize & that this frame is >= fixedFrameSize
182  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, static_cast<FwAssertArgType>(status));
183 
184  // Extract Transfer Frame Version Number (Section 4.1.2.2.2)
185  // AOS uses Tfvn::AOS = 0x1 ('01' binary)
186  U8 tfvn = static_cast<U8>((header.get_globalVcId() & AOSHeaderSubfields::frameVersionMask) >>
188  if (tfvn != static_cast<U8>(Tfvn::AOS)) {
189  this->log_WARNING_HI_InvalidTfvn(tfvn, static_cast<U8>(Tfvn::AOS));
190  this->notifyErrorIfConnected(Ccsds::FrameError::AOS_INVALID_VERSION);
191  return nullptr;
192  }
193 
194  // Extract Spacecraft ID (Section 4.1.2.2)
195  // SCID is split: 8 LS bits in globalVcId, 2 MS bits in signaling field
196  // We extract and do logical OR in a single operation to appease GCC warnings related to int promotion in |=
197  const U16 spacecraftId =
198  static_cast<U16>(((header.get_globalVcId() & AOSHeaderSubfields::spacecraftIdLsbMask) >>
200  ((header.get_frameCountAndSignaling() & AOSHeaderSubfields::spacecraftIdMsbMask)
202 
203  if (spacecraftId != m_spacecraftId) {
204  this->log_WARNING_LO_InvalidSpacecraftId(spacecraftId, m_spacecraftId);
205  this->notifyErrorIfConnected(Ccsds::FrameError::AOS_INVALID_SCID);
206  return nullptr;
207  }
208 
209  // Extract Virtual Channel ID (Section 4.1.2.3)
210  U8 vcId = static_cast<U8>(header.get_globalVcId() & AOSHeaderSubfields::virtualChannelIdMask);
211  AosDeframerVc* vc = this->getVcStruct(vcId);
212 
213  if (vc == nullptr) {
214  // TODO: Multi VC | Handle logging all valid vcIds
215  this->log_ACTIVITY_LO_InvalidVcId(vcId, m_vcs[0].virtualChannelId);
216  this->notifyErrorIfConnected(Ccsds::FrameError::AOS_INVALID_VCID);
217  return vc;
218  }
219 
220  // Extract Virtual Channel Frame Count (Section 4.1.2.4)
221  // 24 bits in the upper 3 bytes of frameCountAndSignaling
222  U32 rxVcFrameCount = (header.get_frameCountAndSignaling() & AOSHeaderSubfields::vcFrameCountMask) >>
224 
225  // Default Frame Count is a 24 bit counter (e.g. modulo 2^24)
226  U32 frameCountMask = 0x00FF'FFFF;
227 
228  // Extract VC Frame Count Cycle if in use (Section 4.1.2.5.3)
229  if ((header.get_frameCountAndSignaling() & AOSHeaderSubfields::cycleCountFlagMask) != 0) {
230  const U8 rxVcFrameCountCycle = header.get_frameCountAndSignaling() & AOSHeaderSubfields::vcFrameCountCycleMask;
231  // Extend the 24-bit frame count with the 4-bit cycle count
232  rxVcFrameCount |= static_cast<U32>(rxVcFrameCountCycle) << 24;
233  // Add the 4 additional bits to our modulo
234  frameCountMask |= 0x0F00'0000;
235  }
236 
237  // Gap detect after the first accepted frame on a VC
238  if (vc->framesProcessed > 0U) {
239  const U32 expectedVcFrameCount = vc->vcFrameCount + 1U;
240  if (rxVcFrameCount != (expectedVcFrameCount & frameCountMask)) {
241  this->log_WARNING_HI_VcFrameCountGap(vcId, rxVcFrameCount, expectedVcFrameCount);
242  this->notifyErrorIfConnected(Ccsds::FrameError::AOS_VC_FRAME_COUNT_GAP);
243  // Other errors will implicitly drop their spanning packet once we finally lock back onto a valid frame
244  this->abandonSpanningPacket(*vc);
245  }
246  }
247 
248  // Store VC frame count in the VC struct for reference (e.g. gap detection)
249  vc->vcFrameCount = rxVcFrameCount;
250  this->tlmWrite_LatestVcFrameCount(vc->vcFrameCount);
251 
252  // Update context with extracted values
253  context.set_vcId(vcId);
254 
255  return vc;
256 }
257 
258 bool AosDeframer::validateFecf(Fw::Buffer& data) {
259  // Per CCSDS 732.0-B-5 Section 4.1.6, FECF is a 16-bit CRC
260  // computed over all preceding bits in the frame
261 
262  const FwSizeType crcDataLen = m_fixedFrameSize - AOSTrailer::SERIALIZED_SIZE;
263  U16 computedCrc = Ccsds::Utils::CRC16::compute(data.getData(), static_cast<U32>(crcDataLen));
264 
265  // Deserialize the trailer
266  AOSTrailer trailer;
267  auto deserializer = data.getDeserializer();
268  deserializer.moveDeserToOffset(crcDataLen);
269  Fw::SerializeStatus status = deserializer.deserializeTo(trailer);
270  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
271 
272  U16 transmittedCrc = trailer.get_fecf();
273  if (transmittedCrc != computedCrc) {
274  this->log_WARNING_HI_InvalidFecf(transmittedCrc, computedCrc);
275  this->notifyErrorIfConnected(Ccsds::FrameError::AOS_INVALID_CRC);
276  this->tlmWrite_CrcErrorCount(++m_crcErrorCount);
277  return false;
278  }
279 
280  return true;
281 }
282 
283 FwSizeType AosDeframer::appendToSpanningPacket(AosDeframerVc& vc, U8* data, FwSizeType size) {
284  FW_ASSERT(data != nullptr);
285  FW_ASSERT(size > 0, static_cast<FwAssertArgType>(size));
286 
287  // How much the outer func needs to seek forward in the AOS frame
288  FwSizeType seekForward = 0;
289 
290  // We work out of the static header buffer until we know the full packet size
291  if (!vc.spanningPacket.buffer.isValid()) {
292  // (Keep) packing whatever we've got into the static header buffer
293  const FwSizeType headerCap = AosDeframerVc::SpanningPacketState::HEADER_BUF_SIZE;
294  // Pack the lesser of how much we have & how much room we have
295  const FwSizeType toHeader = FW_MIN(size, headerCap - vc.spanningPacket.bytesReceived);
296  if (toHeader > 0) {
297  FW_ASSERT(vc.spanningPacket.bytesReceived < headerCap,
298  static_cast<FwAssertArgType>(vc.spanningPacket.bytesReceived),
299  static_cast<FwAssertArgType>(headerCap));
300  FW_ASSERT(toHeader <= headerCap, static_cast<FwAssertArgType>(toHeader),
301  static_cast<FwAssertArgType>(headerCap));
302  FW_ASSERT(vc.spanningPacket.bytesReceived + toHeader <= headerCap,
303  static_cast<FwAssertArgType>(vc.spanningPacket.bytesReceived),
304  static_cast<FwAssertArgType>(toHeader), static_cast<FwAssertArgType>(headerCap));
305  ::memcpy(vc.spanningPacket.headerBuf + vc.spanningPacket.bytesReceived, data, toHeader);
306  vc.spanningPacket.bytesReceived += toHeader;
307 
308  // We'll work w/ everything past the copied header if we get a clean parse
309  data += toHeader;
310  size -= toHeader;
311  seekForward += toHeader;
312  }
313 
314  // Attempt to find a size w/ what we have in our header buff (zero means we ran out of frame before valid
315  // packet)
316  // FIXME: packetSize is untrusted (read straight from the wire) and could have unintended side effects,
317  // especially with EPP. We should skip if packetSize exceeds a mission-defined limit instead of letting the
318  // allocator handle any requests, which has a side effect of abandoning spanning packets on failure
319  const FwSizeType packetSize = sizePacket(vc, vc.spanningPacket.headerBuf, vc.spanningPacket.bytesReceived);
320  if (packetSize == 0) {
321  return 0;
322  }
323 
324  // Try to allocate a buffer for the whole packet
325  vc.spanningPacket.buffer = this->allocate_out(0, packetSize);
326  if (vc.spanningPacket.buffer.getSize() < packetSize) {
327  this->log_WARNING_HI_SpanningPacketAllocFailed(vc.virtualChannelId, vc.spanningPacket.context.get_pvn(),
328  packetSize);
329  // Save before abandon clears it -— needed for the correct seek offset below
330  const FwSizeType remainingBody = packetSize - vc.spanningPacket.bytesReceived;
331  this->abandonSpanningPacket(vc);
332 
333  // Seek past the failed packet (header bytes already consumed + remaining body)
334  const FwSizeType remainingLength = seekForward + remainingBody;
335  if (remainingLength > size) {
336  return 0;
337  } else {
338  return remainingLength;
339  }
340  }
341 
342  // Load the header into the dynamic buffer
343  FW_ASSERT(vc.spanningPacket.bytesReceived <= AosDeframerVc::SpanningPacketState::HEADER_BUF_SIZE,
344  static_cast<FwAssertArgType>(vc.spanningPacket.bytesReceived),
345  AosDeframerVc::SpanningPacketState::HEADER_BUF_SIZE);
346  ::memcpy(vc.spanningPacket.buffer.getData(), vc.spanningPacket.headerBuf, vc.spanningPacket.bytesReceived);
347  }
348 
349  // Already have the dynamic buffer, so fill away
350  const FwSizeType spaceLeft = vc.spanningPacket.buffer.getSize() - vc.spanningPacket.bytesReceived;
351  // Copy what we got
352  const FwSizeType toBody = FW_MIN(size, spaceLeft);
353  if (toBody > 0) {
354  FW_ASSERT(vc.spanningPacket.bytesReceived + toBody <= vc.spanningPacket.buffer.getSize(),
355  static_cast<FwAssertArgType>(vc.spanningPacket.bytesReceived), static_cast<FwAssertArgType>(toBody),
356  static_cast<FwAssertArgType>(vc.spanningPacket.buffer.getSize()));
357  ::memcpy(vc.spanningPacket.buffer.getData() + vc.spanningPacket.bytesReceived, data, toBody);
358  vc.spanningPacket.bytesReceived += toBody;
359  seekForward += toBody;
360  }
361 
362  // Check if the spanning packet is now complete
363  if (vc.spanningPacket.buffer.getSize() > 0 &&
364  vc.spanningPacket.bytesReceived >= vc.spanningPacket.buffer.getSize()) {
365  this->dataOut_out(0, vc.spanningPacket.buffer, vc.spanningPacket.context);
366  this->tlmWrite_PacketsExtracted(++vc.packetsExtracted);
367 
368  // Ownership of the buffer has transferred downstream; clear local handle before consolidating state reset.
369  vc.spanningPacket.buffer = Fw::Buffer();
370  // Buffer won't be returned now since we cleared the handle
371  this->abandonSpanningPacket(vc);
372  }
373 
374  return seekForward;
375 }
376 
377 void AosDeframer::extractPackets(AosDeframerVc& vc, Fw::Buffer& data) {
378  // Parse M_PDU header (per CCSDS 732.0-B-5 Section 4.1.4.2.2)
379  M_PDUHeader mpduHeader;
380  auto deserializer = data.getDeserializer();
381  deserializer.moveDeserToOffset(AOSHeader::SERIALIZED_SIZE);
382  Fw::SerializeStatus status = deserializer.deserializeTo(mpduHeader);
383  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
384 
385  U16 firstHeaderPointer = mpduHeader.get_firstHeaderPointer();
386 
387  // Calculate data zone boundaries
388  const FwSizeType dataZoneStart = AOSHeader::SERIALIZED_SIZE + M_PDUHeader::SERIALIZED_SIZE;
389  const FwSizeType dataZoneEnd = m_fixedFrameSize - (m_fecfEnabled ? AOSTrailer::SERIALIZED_SIZE : 0);
390  const FwSizeType dataZoneSize = dataZoneEnd - dataZoneStart;
391  U8* dataZone = data.getData() + dataZoneStart;
392 
393  // Handle special First Header Pointer values (Section 4.1.4.2.2.4)
394  if (firstHeaderPointer == M_PDUSubfields::FHP_IDLE_DATA_ONLY) {
395  // Frame contains only idle data
396  this->log_ACTIVITY_LO_IdleFrame(vc.virtualChannelId);
397  return;
398  }
399  // Handle continuation data (data before First Header Pointer)
400  else if (firstHeaderPointer == M_PDUSubfields::FHP_NO_PACKET_START) {
401  // Entire data zone is continuation of previous packet
402  if (vc.spanningPacket.bytesReceived > 0) {
403  (void)this->appendToSpanningPacket(vc, dataZone, dataZoneSize);
404  }
405  // If no spanning packet active, this continuation data cannot be used
406  return;
407  }
408 
409  // Guard against First Header Pointer pointing out of bounds (untrusted input)
410  if (firstHeaderPointer >= dataZoneSize) {
411  this->log_WARNING_HI_InvalidFhp(vc.virtualChannelId, firstHeaderPointer, dataZoneSize);
412  this->notifyErrorIfConnected(Ccsds::FrameError::AOS_INVALID_LENGTH);
413  // Abandon any existing data since this frame (and any continuing packets) are garbage now
414  this->abandonSpanningPacket(vc);
415  return;
416  }
417 
418  // There is continuation data before the first packet header
419  if (firstHeaderPointer > 0 && vc.spanningPacket.bytesReceived > 0) {
420  (void)this->appendToSpanningPacket(vc, dataZone, static_cast<FwSizeType>(firstHeaderPointer));
421  // We must be done w/ the prior packet since we have a FHP
422  this->abandonSpanningPacket(vc);
423  }
424 
425  // Move to first packet header
426  FwSizeType currentOffset = firstHeaderPointer;
427 
428  // Max Bound is a sequence of 1 byte EPP Idle Packets
429  const FwIndexType maxIters = static_cast<FwIndexType>(dataZoneSize - firstHeaderPointer);
430 
431  // Extract packets starting at First Header Pointer
432  // (All fresh packets from here on out)
433  for (FwIndexType iter = 0; iter < maxIters && currentOffset < dataZoneSize; iter++) {
434  // Clear out any prior packet data
435  this->abandonSpanningPacket(vc);
436 
437  U8* packetStart = dataZone + currentOffset;
438  FwSizeType remainingBytes = dataZoneSize - currentOffset;
439 
440  FwSizeType packetSize = this->appendToSpanningPacket(vc, packetStart, remainingBytes);
441 
442  if (packetSize == 0) {
443  // Break out of loop since we ran out of data
444  return;
445  }
446 
447  currentOffset += packetSize;
448  }
449 }
450 
451 FwSizeType AosDeframer::sizePacket(AosDeframerVc& vc, U8* packetStart, FwSizeType remainingBytes) {
452  FW_ASSERT(remainingBytes > 0, static_cast<FwAssertArgType>(remainingBytes));
453 
454  // Determine packet type from PVN (upper 3 bits of first byte)
455  U8 pvn = getPacketVersion(packetStart[0]);
456  // Default to invalid, override if valid (non-idle) packet
457  vc.spanningPacket.context.set_pvn(ComCfg::Pvn::INVALID_UNINITIALIZED);
458 
459  // Check if this pvn is disabled
460  if (~vc.pvnMask & (1 << pvn)) {
461  this->log_WARNING_HI_DisabledPvn(vc.virtualChannelId, pvn);
462  return 0;
463  }
464 
465  ComCfg::Pvn pvnEnum = static_cast<ComCfg::Pvn::T>(pvn);
466  vc.spanningPacket.context.set_pvn(pvnEnum);
467 
468  // Size the Packet (so we can alloc a buffer)
469  switch (pvnEnum) {
471  return sizeSppPacket(packetStart, remainingBytes);
472  break;
474  return sizeEppPacket(packetStart, remainingBytes);
475  break;
476  default:
477  // User should only configure AOS Deframer to accept SPP &/| EPP
478  FW_ASSERT(false, pvn);
479  return 0;
480  }
481 }
482 
483 FwSizeType AosDeframer::sizeSppPacket(U8* payloadStart, FwSizeType payloadSize) {
484  SpacePacketHeader header;
485 
486  Fw::Buffer data(payloadStart, payloadSize);
487  Fw::SerializeStatus status = data.getDeserializer().deserializeTo(header);
488 
489  if (status != Fw::FW_SERIALIZE_OK) {
490  return 0; // Incomplete header - spans to next frame
491  }
492 
493  // Per CCSDS 133.0-B-2 Section 4.1.3.5.2, packet data length = (actual length - 1)
494  FwSizeType totalPacketSize = SpacePacketHeader::SERIALIZED_SIZE + header.get_packetDataLength() + 1;
495 
496  // TODO: Unify Deframers | bring the whole spp processing into this component
497  // since we're only missing seq count logic?
498 
499  // Check for idle packet (APID = 0x7FF per CCSDS 133.0-B-2)
500  U16 apid = static_cast<U16>(header.get_packetIdentification() & SpacePacketSubfields::ApidMask);
501 
502  // Idle means this is the last packet in the frame
503  if (apid == static_cast<U16>(ComCfg::Apid::SPP_IDLE_PACKET)) {
504  return 0;
505  }
506 
507  return totalPacketSize;
508 }
509 
510 FwSizeType AosDeframer::sizeEppPacket(const U8* const payloadStart, FwSizeType payloadSize) {
511  // Per CCSDS 133.1-B-3 Section 4.1.2.1.1, EPP minimum header is 1 byte
512  // Since we identified this as an EPP we had the 1 byte to read the PVN already
513  FW_ASSERT(payloadSize > 0, static_cast<FwAssertArgType>(payloadSize));
514 
515  // Parse first byte
516  U8 firstByte = payloadStart[0];
517  U8 protocolId = static_cast<U8>((firstByte & EPPSubfields::protocolIdMask) >> EPPSubfields::protocolIdOffset);
518 
519  FwSizeType totalPacketSize = 0;
520 
521  // Idle means this is the last packet in the frame
522  if (protocolId == static_cast<U8>(EppProtocolId::Idle)) {
523  return 0;
524  }
525 
526  // Encapsulation Idle Packet per CCSDS 133.1-B-3 Section 4.1.3.2
527  U8 lengthOfLength = firstByte & EPPSubfields::lengthOfLengthMask;
528 
529  U8 lengthOffset = 1U;
530 
531  // If length of length is 2 or more then there's an extra byte of extension/user defined (4.1.2.1.1)
532  if (lengthOfLength >= EppLengthOfLength::Two) {
533  lengthOffset = static_cast<U8>(lengthOffset + 1U);
534  }
535 
536  // If length of length is 4 then we add 2 bytes for the ccsds reserved field (4.1.2.1.1)
537  if (lengthOfLength == EppLengthOfLength::Four) {
538  lengthOffset = static_cast<U8>(lengthOffset + 2U);
539  // '0d3' on the wire, but means 4
540  lengthOfLength = 4;
541  }
542 
543  // Bytes to get to length + length of length
544  const U8 headerLength = static_cast<U8>(lengthOffset + lengthOfLength);
545 
546  // Validate and read length field
547  if (payloadSize < headerLength) {
548  return 0; // Incomplete
549  }
550 
551  // Read length field (big-endian)
552  U32 packetDataLength = 0;
553  for (U8 i = 0; i < lengthOfLength; i++) {
554  packetDataLength = (packetDataLength << 8) | payloadStart[lengthOffset + i];
555  }
556 
557  totalPacketSize = headerLength + packetDataLength;
558 
559  return totalPacketSize;
560 }
561 
562 U8 AosDeframer::getPacketVersion(U8 firstByte) {
563  // PVN is the upper 3 bits per both CCSDS 133.0-B-2 and 133.1-B-3
564  // EPP's Subfield array is done in bytes
565  return static_cast<U8>(firstByte >> EPPSubfields::packetVersionOffset);
566 }
567 
568 } // namespace Ccsds
569 } // namespace Svc
Serialization/Deserialization operation was successful.
static U16 compute(const U8 *buffer, U32 length)
compute CRC16 for a buffer
Definition: CRC16.hpp:43
CCSDS 732.0-B-5: Transfer Frame Version Number mismatch (4.1.2.2.2)
Fully Featured CCSDS Space Packet Protocol.
Definition: PvnEnumAc.hpp:33
Advanced Orbiting Systems SDL.
Definition: TfvnEnumAc.hpp:45
PlatformSizeType FwSizeType
CCSDS 732.0-B-5: Frame length insufficient.
CCSDS 732.0-B-5: Frame Error Control Field CRC mismatch (4.1.6)
Anything equal or higher value is invalid and should not be used.
Definition: PvnEnumAc.hpp:37
T
The raw enum type.
Definition: PvnEnumAc.hpp:31
~AosDeframer()
Destroy AosDeframer object.
Definition: AosDeframer.cpp:40
The size of the serial representation.
U8 * getData() const
Definition: Buffer.cpp:56
The size of the serial representation.
void configure(U32 fixedFrameSize, bool frameErrorControlField, U16 spacecraftId=ComCfg::SpacecraftId, U8 vcId=0, U8 pvnMask=PvnBitfield::SPP_MASK|PvnBitfield::EPP_MASK)
Configure the AosDeframer with mission-specific parameters.
Definition: AosDeframer.cpp:42
bool isConnected_errorNotify_OutputPort(FwIndexType portNum) const
Auto-generated base for AosDeframer component.
CCSDS 732.0-B-5: Virtual Channel ID mismatch (4.1.2.3)
void log_WARNING_LO_InvalidSpacecraftId(U16 transmitted, U16 configured) const
SerializeStatus
forward declaration for string
ExternalSerializeBufferWithMemberCopy getDeserializer()
Definition: Buffer.cpp:105
bool isConnected_allocate_OutputPort(FwIndexType portNum) const
void tlmWrite_FramesProcessed(U32 arg, Fw::Time _tlmTime=Fw::Time()) const
void log_WARNING_HI_InvalidFrameLength(FwSizeType actual, U32 expected) const
#define FW_MIN(a, b)
MIN macro (deprecated in C++, use std::min)
Definition: BasicTypes.h:94
void deallocate_out(FwIndexType portNum, Fw::Buffer &fwBuffer) const
Invoke output port deallocate.
void log_WARNING_HI_SpanningPacketAbandoned(U8 vcId, ComCfg::Pvn pvn, FwSizeType bytesReceived, FwSizeType bytesExpected) const
CCSDS 732.0-B-5: Spacecraft ID mismatch (4.1.2.2)
Packet Version Numbers are 3 bits with only 2 currently valid values.
Definition: PvnEnumAc.hpp:17
bool isConnected_deallocate_OutputPort(FwIndexType portNum) const
void set_vcId(U8 vcId)
Set member vcId.
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:53
void errorNotify_out(FwIndexType portNum, const Svc::Ccsds::FrameError &errorCode) const
Invoke output port errorNotify.
FwSizeType getSize() const
Definition: Buffer.cpp:60
AosDeframer(const char *const compName)
Construct AosDeframer object.
Definition: AosDeframer.cpp:28
Bare-bones CCSDS Encapsulation Packet Protocol.
Definition: PvnEnumAc.hpp:35
The size of the serial representation.
PlatformIndexType FwIndexType
SerializeStatus moveDeserToOffset(FwSizeType offset) override
Move deserialization pointer to specified offset.
void log_ACTIVITY_LO_InvalidVcId(U8 transmitted, U8 configured) const
Type used to pass context info between components during framing/deframing.
RateGroupDivider component implementation.
SerializeStatus deserializeTo(U8 &val, Endianness mode=Endianness::BIG) override
Deserialize an 8-bit unsigned integer value.
Per Space Packet Standard, all 1s (11bits) is reserved for Idle Packets.
Definition: ApidEnumAc.hpp:51
void dataReturnOut_out(FwIndexType portNum, Fw::Buffer &data, const ComCfg::FrameContext &context) const
Invoke output port dataReturnOut.
#define FW_ASSERT(...)
Definition: Assert.hpp:14
PlatformAssertArgType FwAssertArgType
The type of arguments to assert functions.
void log_WARNING_HI_InvalidTfvn(U8 transmitted, U8 expected) const
CCSDS 732.0-B-5: AOS VC frame count discontinuity detected.