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 // ======================================================================
13 #include <cstring>
14 #include <limits>
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  const FwSizeType packetSize = sizePacket(vc, vc.spanningPacket.headerBuf, vc.spanningPacket.bytesReceived);
317  if (packetSize == 0) {
318  return 0;
319  }
320 
321  // Try to allocate a buffer for the whole packet. If this size is invalid (too large) or if the buffer
322  // manager is out of memory, this is handled below.
323  vc.spanningPacket.buffer = this->allocate_out(0, packetSize);
324  if ((not vc.spanningPacket.buffer.isValid()) || (vc.spanningPacket.buffer.getSize() < packetSize)) {
325  this->log_WARNING_HI_SpanningPacketAllocFailed(vc.virtualChannelId, vc.spanningPacket.context.get_pvn(),
326  packetSize);
327  // Save before abandon clears it -— needed for the correct seek offset below
328  const FwSizeType remainingBody = packetSize - vc.spanningPacket.bytesReceived;
329  this->abandonSpanningPacket(vc);
330 
331  // Seek past the failed packet (header bytes already consumed + remaining body)
332  const FwSizeType remainingLength = seekForward + remainingBody;
333  if (remainingLength > size) {
334  return 0;
335  } else {
336  return remainingLength;
337  }
338  }
339 
340  // Load the header into the dynamic buffer
341  FW_ASSERT(vc.spanningPacket.bytesReceived <= AosDeframerVc::SpanningPacketState::HEADER_BUF_SIZE,
342  static_cast<FwAssertArgType>(vc.spanningPacket.bytesReceived),
343  AosDeframerVc::SpanningPacketState::HEADER_BUF_SIZE);
344  // Destination buffer must be large enough for the accumulated header bytes.
345  // Protect against any future regression in sizeEppPacket/sizeSppPacket
346  // reintroducing an overflow that makes packetSize < bytesReceived.
347  FW_ASSERT(vc.spanningPacket.bytesReceived <= vc.spanningPacket.buffer.getSize(),
348  static_cast<FwAssertArgType>(vc.spanningPacket.bytesReceived),
349  static_cast<FwAssertArgType>(vc.spanningPacket.buffer.getSize()));
350  ::memcpy(vc.spanningPacket.buffer.getData(), vc.spanningPacket.headerBuf, vc.spanningPacket.bytesReceived);
351  }
352 
353  // Already have the dynamic buffer, so fill away
354  const FwSizeType spaceLeft = vc.spanningPacket.buffer.getSize() - vc.spanningPacket.bytesReceived;
355  // Copy what we got
356  const FwSizeType toBody = FW_MIN(size, spaceLeft);
357  if (toBody > 0) {
358  FW_ASSERT(vc.spanningPacket.bytesReceived + toBody <= vc.spanningPacket.buffer.getSize(),
359  static_cast<FwAssertArgType>(vc.spanningPacket.bytesReceived), static_cast<FwAssertArgType>(toBody),
360  static_cast<FwAssertArgType>(vc.spanningPacket.buffer.getSize()));
361  ::memcpy(vc.spanningPacket.buffer.getData() + vc.spanningPacket.bytesReceived, data, toBody);
362  vc.spanningPacket.bytesReceived += toBody;
363  seekForward += toBody;
364  }
365 
366  // Check if the spanning packet is now complete
367  if (vc.spanningPacket.buffer.getSize() > 0 &&
368  vc.spanningPacket.bytesReceived >= vc.spanningPacket.buffer.getSize()) {
369  this->dataOut_out(0, vc.spanningPacket.buffer, vc.spanningPacket.context);
370  this->tlmWrite_PacketsExtracted(++vc.packetsExtracted);
371 
372  // Ownership of the buffer has transferred downstream; clear local handle before consolidating state reset.
373  vc.spanningPacket.buffer = Fw::Buffer();
374  // Buffer won't be returned now since we cleared the handle
375  this->abandonSpanningPacket(vc);
376  }
377 
378  return seekForward;
379 }
380 
381 void AosDeframer::extractPackets(AosDeframerVc& vc, Fw::Buffer& data) {
382  // Parse M_PDU header (per CCSDS 732.0-B-5 Section 4.1.4.2.2)
383  M_PDUHeader mpduHeader;
384  auto deserializer = data.getDeserializer();
385  deserializer.moveDeserToOffset(AOSHeader::SERIALIZED_SIZE);
386  Fw::SerializeStatus status = deserializer.deserializeTo(mpduHeader);
387  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
388 
389  U16 firstHeaderPointer = mpduHeader.get_firstHeaderPointer();
390 
391  // Calculate data zone boundaries
392  const FwSizeType dataZoneStart = AOSHeader::SERIALIZED_SIZE + M_PDUHeader::SERIALIZED_SIZE;
393  const FwSizeType dataZoneEnd = m_fixedFrameSize - (m_fecfEnabled ? AOSTrailer::SERIALIZED_SIZE : 0);
394  const FwSizeType dataZoneSize = dataZoneEnd - dataZoneStart;
395  U8* dataZone = data.getData() + dataZoneStart;
396 
397  // Handle special First Header Pointer values (Section 4.1.4.2.2.4)
398  if (firstHeaderPointer == M_PDUSubfields::FHP_IDLE_DATA_ONLY) {
399  // Frame contains only idle data
400  this->log_ACTIVITY_LO_IdleFrame(vc.virtualChannelId);
401  return;
402  }
403  // Handle continuation data (data before First Header Pointer)
404  else if (firstHeaderPointer == M_PDUSubfields::FHP_NO_PACKET_START) {
405  // Entire data zone is continuation of previous packet
406  if (vc.spanningPacket.bytesReceived > 0) {
407  (void)this->appendToSpanningPacket(vc, dataZone, dataZoneSize);
408  }
409  // If no spanning packet active, this continuation data cannot be used
410  return;
411  }
412 
413  // Guard against First Header Pointer pointing out of bounds (untrusted input)
414  if (firstHeaderPointer >= dataZoneSize) {
415  this->log_WARNING_HI_InvalidFhp(vc.virtualChannelId, firstHeaderPointer, dataZoneSize);
416  this->notifyErrorIfConnected(Ccsds::FrameError::AOS_INVALID_LENGTH);
417  // Abandon any existing data since this frame (and any continuing packets) are garbage now
418  this->abandonSpanningPacket(vc);
419  return;
420  }
421 
422  // There is continuation data before the first packet header
423  if (firstHeaderPointer > 0 && vc.spanningPacket.bytesReceived > 0) {
424  (void)this->appendToSpanningPacket(vc, dataZone, static_cast<FwSizeType>(firstHeaderPointer));
425  // We must be done w/ the prior packet since we have a FHP
426  this->abandonSpanningPacket(vc);
427  }
428 
429  // Move to first packet header
430  FwSizeType currentOffset = firstHeaderPointer;
431 
432  // Max Bound is a sequence of 1 byte EPP Idle Packets
433  const FwIndexType maxIters = static_cast<FwIndexType>(dataZoneSize - firstHeaderPointer);
434 
435  // Extract packets starting at First Header Pointer
436  // (All fresh packets from here on out)
437  for (FwIndexType iter = 0; iter < maxIters && currentOffset < dataZoneSize; iter++) {
438  // Clear out any prior packet data
439  this->abandonSpanningPacket(vc);
440 
441  U8* packetStart = dataZone + currentOffset;
442  FwSizeType remainingBytes = dataZoneSize - currentOffset;
443 
444  FwSizeType packetSize = this->appendToSpanningPacket(vc, packetStart, remainingBytes);
445 
446  if (packetSize == 0) {
447  // Break out of loop since we ran out of data
448  return;
449  }
450 
451  currentOffset += packetSize;
452  }
453 }
454 
455 FwSizeType AosDeframer::sizePacket(AosDeframerVc& vc, U8* packetStart, FwSizeType remainingBytes) {
456  FW_ASSERT(remainingBytes > 0, static_cast<FwAssertArgType>(remainingBytes));
457 
458  // Determine packet type from PVN (upper 3 bits of first byte)
459  U8 pvn = getPacketVersion(packetStart[0]);
460  // Default to invalid, override if valid (non-idle) packet
461  vc.spanningPacket.context.set_pvn(ComCfg::Pvn::INVALID_UNINITIALIZED);
462 
463  // Check if this pvn is disabled
464  if (~vc.pvnMask & (1 << pvn)) {
465  this->log_WARNING_HI_DisabledPvn(vc.virtualChannelId, pvn);
466  return 0;
467  }
468 
469  ComCfg::Pvn pvnEnum = static_cast<ComCfg::Pvn::T>(pvn);
470  vc.spanningPacket.context.set_pvn(pvnEnum);
471 
472  // Size the Packet (so we can alloc a buffer)
473  switch (pvnEnum) {
475  return sizeSppPacket(packetStart, remainingBytes);
476  break;
478  return sizeEppPacket(packetStart, remainingBytes);
479  break;
480  default:
481  // User should only configure AOS Deframer to accept SPP &/| EPP
482  FW_ASSERT(false, pvn);
483  return 0;
484  }
485 }
486 
487 FwSizeType AosDeframer::sizeSppPacket(U8* payloadStart, FwSizeType payloadSize) {
488  SpacePacketHeader header;
489 
490  Fw::Buffer data(payloadStart, payloadSize);
491  Fw::SerializeStatus status = data.getDeserializer().deserializeTo(header);
492 
493  if (status != Fw::FW_SERIALIZE_OK) {
494  return 0; // Incomplete header - spans to next frame
495  }
496 
497  // Per CCSDS 133.0-B-2 Section 4.1.3.5.2, packet data length = (actual length - 1)
498  // packetDataLength is a 16-bit field (max 65535); SERIALIZED_SIZE is a small constant.
499  // Guarantee at compile time that the maximum possible sum fits in FwSizeType. If
500  // FwSizeType is ever narrowed below 17 bits, this fails to build and the addition
501  // below must be guarded the same way sizeEppPacket is.
502  constexpr FwSizeType MAX_LENGTH = std::numeric_limits<FwSizeType>::max() - SpacePacketHeader::SERIALIZED_SIZE;
503  static_assert(MAX_LENGTH >= std::numeric_limits<U16>::max() + 1,
504  "FwSizeType must be wide enough to hold the maximum SPP packet size without overflow");
505  FwSizeType totalPacketSize = SpacePacketHeader::SERIALIZED_SIZE + header.get_packetDataLength() + 1;
506 
507  // TODO: Unify Deframers | bring the whole spp processing into this component
508  // since we're only missing seq count logic?
509 
510  // Check for idle packet (APID = 0x7FF per CCSDS 133.0-B-2)
511  U16 apid = static_cast<U16>(header.get_packetIdentification() & SpacePacketSubfields::ApidMask);
512 
513  // Idle means this is the last packet in the frame
514  if (apid == static_cast<U16>(ComCfg::Apid::SPP_IDLE_PACKET)) {
515  return 0;
516  }
517 
518  return totalPacketSize;
519 }
520 
521 FwSizeType AosDeframer::sizeEppPacket(const U8* const payloadStart, FwSizeType payloadSize) {
522  // Per CCSDS 133.1-B-3 Section 4.1.2.1.1, EPP minimum header is 1 byte
523  // Since we identified this as an EPP we had the 1 byte to read the PVN already
524  FW_ASSERT(payloadSize > 0, static_cast<FwAssertArgType>(payloadSize));
525 
526  // Parse first byte
527  U8 firstByte = payloadStart[0];
528  U8 protocolId = static_cast<U8>((firstByte & EPPSubfields::protocolIdMask) >> EPPSubfields::protocolIdOffset);
529 
530  FwSizeType totalPacketSize = 0;
531 
532  // Idle means this is the last packet in the frame
533  if (protocolId == static_cast<U8>(EppProtocolId::Idle)) {
534  return 0;
535  }
536 
537  // Encapsulation Idle Packet per CCSDS 133.1-B-3 Section 4.1.3.2
538  U8 lengthOfLength = firstByte & EPPSubfields::lengthOfLengthMask;
539 
540  U8 lengthOffset = 1U;
541 
542  // If length of length is 2 or more then there's an extra byte of extension/user defined (4.1.2.1.1)
543  if (lengthOfLength >= EppLengthOfLength::Two) {
544  lengthOffset = static_cast<U8>(lengthOffset + 1U);
545  }
546 
547  // If length of length is 4 then we add 2 bytes for the ccsds reserved field (4.1.2.1.1)
548  if (lengthOfLength == EppLengthOfLength::Four) {
549  lengthOffset = static_cast<U8>(lengthOffset + 2U);
550  // '0d3' on the wire, but means 4
551  lengthOfLength = 4;
552  }
553 
554  // Bytes to get to length + length of length
555  const U8 headerLength = static_cast<U8>(lengthOffset + lengthOfLength);
556 
557  // Validate and read length field
558  if (payloadSize < headerLength) {
559  return 0; // Incomplete
560  }
561 
562  // Read length field (big-endian)
563  U32 packetDataLength = 0;
564  for (U8 i = 0; i < lengthOfLength; i++) {
565  packetDataLength = (packetDataLength << 8) | payloadStart[lengthOffset + i];
566  }
567 
568  // Guard against integer overflow and return 0 as incomplete/invalid (if true).
569  // This fires on 32-bit targets (FwSizeType = U32) where the sum would wrap.
570  if (packetDataLength > (std::numeric_limits<FwSizeType>::max() - static_cast<FwSizeType>(headerLength))) {
571  return 0;
572  }
573 
574  // Cast both operands to FwSizeType BEFORE adding.
575  // Without the cast, C++ computes headerLength(U8) + packetDataLength(U32) in U32
576  // arithmetic, which wraps on both 32-bit and 64-bit hosts even when FwSizeType is
577  // 64 bits wide. The guard above is not sufficient on its own: on 64-bit it never
578  // fires (packetDataLength can never exceed UINT64_MAX-8), so the unguarded addition
579  // would still silently truncate to 4 on a 64-bit host.
580  totalPacketSize = static_cast<FwSizeType>(headerLength) + static_cast<FwSizeType>(packetDataLength);
581 
582  return totalPacketSize;
583 }
584 
585 U8 AosDeframer::getPacketVersion(U8 firstByte) {
586  // PVN is the upper 3 bits per both CCSDS 133.0-B-2 and 133.1-B-3
587  // EPP's Subfield array is done in bytes
588  return static_cast<U8>(firstByte >> EPPSubfields::packetVersionOffset);
589 }
590 
591 } // namespace Ccsds
592 } // 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
U8 * getData() const
Definition: Buffer.cpp:56
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
The size of the serial representation.
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:99
The size of the serial representation.
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:54
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
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.
The size of the serial representation.
#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.