F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
AosFramer.cpp
Go to the documentation of this file.
1 // ======================================================================
2 // \title AosFramer.cpp
3 // \author Will MacCormack (Aos Modifications)
4 // \brief cpp file for AosFramer component implementation class
5 // \details modified from thomas-bc's TmFramer
6 // ======================================================================
7 
11 
12 namespace Svc {
13 
14 namespace Ccsds {
15 // ----------------------------------------------------------------------
16 // Component construction and destruction
17 // ----------------------------------------------------------------------
18 
19 AosFramer ::AosFramer(const char* const compName) : AosFramerComponentBase(compName) {
20  // Default to FECF on, Max Sized if you don't override w/ another configure call
22 }
23 
25 
26 void AosFramer::configure(const U32 fixedFrameSize,
27  const bool frameErrorControlField,
28  const U16 spacecraftId,
29  const U8 vcId,
30  const U8 idlePvns) {
31  // fixedFrameSize must be less than or equal to the maximum defined in ComCfg.fpp
32  FW_ASSERT(fixedFrameSize <= ComCfg::AosMaxFrameFixedSize, static_cast<FwAssertArgType>(fixedFrameSize));
33 
34  // AOS Frame Fixed Size must be at least large enough to hold header, trailer and data
36  (frameErrorControlField ? AOSTrailer::SERIALIZED_SIZE : 0),
37  static_cast<FwAssertArgType>(fixedFrameSize));
38 
39  // Spacecraft ID is 10 bits (per CCSDS 732.0-B-5 Section 4.1.2.2)
40  FW_ASSERT((spacecraftId & 0xFC00) == 0, static_cast<FwAssertArgType>(spacecraftId));
41 
42  // Virtual Channel ID is 6 bits (per CCSDS 732.0-B-5 Section 4.1.2.3)
43  FW_ASSERT((vcId & 0xC0) == 0, static_cast<FwAssertArgType>(vcId));
44 
45  // AOS Framer must be provided with a protocol to use for Idle Packets
46  // Currently, only SPP idle packing is supported
47  // EPP is more optimal, however
48  FW_ASSERT(idlePvns & PvnBitfield::SPP_MASK, static_cast<FwAssertArgType>(idlePvns));
49 
50  // FECF is constant for a given Physical Channel during a Mission Phase (4.1.6.1.3)
51  this->m_fecf = frameErrorControlField;
52  this->m_spacecraftId = spacecraftId;
53 
54  // For each vc, init the buffer objects
55  for (U8 ind = 0; ind < sizeof(this->m_vcs) / sizeof(AosVc); ind++) {
56  AosVc& currentVc = this->m_vcs[ind];
57 
58  // Write the index for mapping a vc struct onto an output port
59  currentVc.vc_struct_index = ind;
60  currentVc.virtualChannelId = vcId;
61  currentVc.frame.buffer = {currentVc.frame.backer, fixedFrameSize};
62  // Set the bitmask of PVNs to use for idle packets
63  currentVc.idle_packet_types = idlePvns;
64  }
65 }
66 
67 // ----------------------------------------------------------------------
68 // Handler implementations for typed input ports
69 // ----------------------------------------------------------------------
70 
71 void AosFramer ::dataIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context) {
72  // Get the VC Struct for this buffer
73  const U8 vcId = context.get_vcId();
74  AosVc& currentVc = this->get_vc_struct(context);
75  FW_ASSERT(currentVc.virtualChannelId == vcId);
76 
77  // Ensure rest of the com stack is complying with the [communications adapter
78  // interface](docs/reference/communication-adapter-interface.md)
79  FW_ASSERT(currentVc.frame.state == BufferOwnershipState::OWNED,
80  static_cast<FwAssertArgType>(currentVc.frame.state));
81 
82  // AOS Header & M_PDU Header
83  // If this is the first fresh packet, set the M_PDU firstHeaderPointer
84  if (!currentVc.past_first_fresh_packet) {
85  // setup our headers if we have a packet + context ready to go
86  setup_header(context);
87  setup_m_pdu_header(context);
88 
89  // We now will only work with fresh packets for the rest of this frame
90  currentVc.past_first_fresh_packet = true;
91  }
92 
93  // Pack this packet
94  pack_pad_send(data, context);
95 }
96 
97 void AosFramer::compute_and_inject_fecf(AosVc& currentVc) {
98  // -------------------------------------------------
99  // Trailer (CRC)
100  // -------------------------------------------------
101  AOSTrailer trailer;
102 
103  const FwSizeType fecfStart = currentVc.frame.buffer.getSize() - AOSTrailer::SERIALIZED_SIZE;
104  // Compute CRC over the entire frame buffer minus the FECF trailer (Standard 4.1.6)
105  U16 crc = Ccsds::Utils::CRC16::compute(currentVc.frame.buffer.getData(), static_cast<U32>(fecfStart));
106  // Set the Frame Error Control Field (FECF)
107  trailer.set_fecf(crc);
108  // Move the serializer pointer to the end of the location where the trailer will be
109  // serialized
110  auto frameSerializer = currentVc.frame.buffer.getSerializer();
111  Fw::SerializeStatus status = frameSerializer.moveSerToOffset(fecfStart);
112  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
113 
114  status = frameSerializer.serializeFrom(trailer);
115  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
116 }
117 
118 void AosFramer ::comStatusIn_handler(FwIndexType portNum, Fw::Success& condition) {
119  // We just ask upstream for more packets
120  // comQueue decides to which VCs to allocate comStatus success
121  if (this->isConnected_comStatusOut_OutputPort(portNum)) {
122  // Forward the comStatus upstream
123  this->comStatusOut_out(portNum, condition);
124  }
125 }
126 
127 void AosFramer ::dataReturnIn_handler(FwIndexType portNum,
128  Fw::Buffer& frameBuffer,
129  const ComCfg::FrameContext& context) {
130  // Get the VC Struct for this buffer
131  AosVc& currentVc = this->get_vc_struct(context);
132 
133  // Assert that the returned buffer is the member, and set ownership state
134  FW_ASSERT(buffer_belongs(frameBuffer, currentVc.frame.backer, sizeof(currentVc.frame.backer)));
135  currentVc.frame.state = BufferOwnershipState::OWNED;
136 
137  // If we have an outstanding packet from the prior frame, pack it
138  if (currentVc.outstanding.packet.isValid()) {
139  this->pack_pad_send(currentVc.outstanding.packet, currentVc.outstanding.context, currentVc.outstanding.offset);
140  }
141 }
142 
143 AosFramer::AosVc& AosFramer ::get_vc_struct(const ComCfg::FrameContext& context) {
144  // MultiVc support would require looking up a vc struct ind given the vcIndex
145  // (unless you force them to be continuous and start at zero)
146  const U8 ind = 0;
147  AosVc& currentVc = this->m_vcs[ind];
148 
149  // Ensure configure was called
150  FW_ASSERT(currentVc.vc_struct_index == ind);
151  FW_ASSERT(currentVc.virtualChannelId == context.get_vcId());
152  return currentVc;
153 }
154 
155 void AosFramer ::setup_header(const ComCfg::FrameContext& context) {
156  // Get the VC Struct for this vc
157  AosVc& currentVc = this->get_vc_struct(context);
158 
159  // -----------------------------------------------
160  // Header
161  // -----------------------------------------------
162  AOSHeader header;
163 
164  // GVCID (Global Virtual Channel ID) (Standard 4.1.2.2 and 4.1.2.3)
165  U16 globalVcId = static_cast<U16>((context.get_vcId() << AOSHeaderSubfields::virtualChannelIdOffset) |
166  ((m_spacecraftId & 0x00FF) << AOSHeaderSubfields::spacecraftIdLsbOffset) |
168 
169  // Virtual Channel Frame Count (4.1.2.4)
170  U32 frameCountAndSignaling = static_cast<U32>((currentVc.virtualFrameCount & 0x00FFFFFFU)
171  << static_cast<FwSizeType>(AOSHeaderSubfields::vcFrameCountOffset));
172 
173  // Virtual Channel Frame Count Cycle Use Flag (4.1.2.5.3)
174  frameCountAndSignaling |= static_cast<U32>(1 << AOSHeaderSubfields::cycleCountFlagOffset);
175 
176  // Spacecraft ID MSB (4.1.2.5.4)
177  frameCountAndSignaling |=
178  static_cast<U32>((m_spacecraftId & 0x0300) >> (8 - AOSHeaderSubfields::spacecraftIdMsbOffset));
179 
180  // Virtual Channel Frame Cycle Count (4.1.2.5.5)
181  frameCountAndSignaling |= static_cast<U32>((currentVc.virtualFrameCount & 0x0F000000) >> 24);
182 
183  header.set_globalVcId(globalVcId);
184  header.set_frameCountAndSignaling(frameCountAndSignaling);
185 
186  // Perform the modulo at serialization time makes vc cycle count easier to make optional
187  currentVc.virtualFrameCount++; // U24 base Frame Count
188  // Extended by 4 bit VC Cycle Count
189  // intended to wrap around (modulo 268,435,456)
190 
191  // -----------------------------------------------
192  // Write Header
193  // -----------------------------------------------
194  Fw::SerializeStatus status;
195  // Use our member Fw::Buffer
196  auto frameSerializer = currentVc.frame.buffer.getSerializer();
197 
198  status = frameSerializer.moveSerToOffset(0);
199  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
200 
201  status = frameSerializer.serializeFrom(header);
202  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
203 }
204 
205 void AosFramer ::setup_m_pdu_header(const ComCfg::FrameContext& context, bool no_fresh) {
206  // Get the VC Struct for this vc
207  AosVc& currentVc = this->get_vc_struct(context);
208 
209  M_PDUHeader muxedPdu;
210  if (no_fresh) {
211  // All Ones since no packets start in this frame (no fresh packets)
212  muxedPdu.set_firstHeaderPointer(0xFFFF);
213  } else {
214  // Called at first fresh packet (not a packet tail)
215  // So current payload offset is the pointer to first packet header
216  muxedPdu.set_firstHeaderPointer(currentVc.current_payload_offset);
217  }
218 
219  auto frameSerializer = currentVc.frame.buffer.getSerializer();
220  Fw::SerializeStatus status = frameSerializer.moveSerToOffset(AOSHeader::SERIALIZED_SIZE);
221  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
222 
223  frameSerializer.serializeFrom(muxedPdu);
224  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
225 }
226 
227 FwSizeType AosFramer::get_min_size() {
229 }
230 
231 bool AosFramer::buffer_belongs(Fw::Buffer& buffer, U8 const* start, FwSizeType size) {
232  return (buffer.getData() >= start && buffer.getData() < start + size);
233 }
234 
235 void AosFramer::check_and_send_vc(AosFramer::AosVc& currentVc) {
236  const FwSizeType maxPayload = currentVc.frame.buffer.getSize() - get_min_size();
237 
238  // Check if we've filled up the M_PDU payload
239  if (currentVc.current_payload_offset == maxPayload) {
240  // Write the headers if we haven't already (continuing packet only)
241  if (!currentVc.past_first_fresh_packet) {
242  // Setup the AOS header right before we send with the context cached in outstanding
243  setup_header(currentVc.outstanding.context);
244 
245  // Setup the M_PDU header w/ all zero (only continuing packets)
246  setup_m_pdu_header(currentVc.outstanding.context, true);
247  }
248 
249  // Compute Trailer
250  if (this->m_fecf) {
251  compute_and_inject_fecf(currentVc);
252  }
253 
254  // Ensure we aren't double sending
255  FW_ASSERT(currentVc.frame.state == BufferOwnershipState::OWNED);
256  currentVc.frame.state = BufferOwnershipState::NOT_OWNED;
257 
258  // Clean up our per frame vc values
259  currentVc.current_payload_offset = 0;
260  currentVc.past_first_fresh_packet = false;
261 
262  // Write the completed frame
263  this->dataOut_out(0, currentVc.frame.buffer, currentVc.outstanding.context);
264  } else if (this->isConnected_comStatusOut_OutputPort(currentVc.vc_struct_index)) {
265  // We got more room, so ask for more bytes
266  Fw::Success condition = Fw::Success::SUCCESS;
267  this->comStatusOut_out(currentVc.vc_struct_index, condition);
268  }
269 }
270 
271 void AosFramer ::pack_pad_send(Fw::Buffer& data, const ComCfg::FrameContext& context, FwSizeType dataOffset) {
272  // Pack this packet into the M_PDU
273  pack_packet(data, context, dataOffset);
274 
275  // Get the VC Struct for this buffer
276  AosVc& currentVc = this->get_vc_struct(context);
277 
278  // Pack with idle packets if sendNow and not full already
279  // TODO: Add configurable time elapsed & buffer remaining based idle packing
280  if (context.get_sendNow()) {
281  // Compute some sizes to check if we've got space left to pad
282  const FwSizeType min_size = get_min_size();
283  const FwSizeType maxPayload = currentVc.frame.buffer.getSize() - min_size;
284 
285  if (currentVc.current_payload_offset < maxPayload) {
286  // As per TM Standard 4.2.2.5, fill the rest of the data field with an Idle Packet
287  fill_with_idle_packet(currentVc, context);
288  }
289  }
290 
291  // Send the frame if we've filled it
292  check_and_send_vc(currentVc);
293 }
294 
295 void AosFramer ::pack_packet(Fw::Buffer& data, const ComCfg::FrameContext& context, FwSizeType dataOffset) {
296  // Ensure the packet is starting within the frame
297  const FwSizeType min_size = get_min_size();
298 
299  // Get the VC Struct for this buffer
300  AosVc& currentVc = this->get_vc_struct(context);
301 
302  const FwSizeType maxPayload = currentVc.frame.buffer.getSize() - min_size;
303  const FwSizeType bytesAvailable = maxPayload - currentVc.current_payload_offset;
304  FW_ASSERT(bytesAvailable < currentVc.frame.buffer.getSize(),
305  static_cast<FwAssertArgType>(min_size + currentVc.current_payload_offset));
306 
307  // -------------------------------------------------
308  // Payload Packet
309  // -------------------------------------------------
310  Fw::SerializeStatus status;
311  // Use our member Fw::Buffer
312  auto frameSerializer = currentVc.frame.buffer.getSerializer();
313  status = frameSerializer.moveSerToOffset(START_OF_PAYLOAD + currentVc.current_payload_offset);
314  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
315 
316  const U8* dataStart = data.getData() + dataOffset;
317  // min of (remaining bytes in buffer and available bytes in frame)
318  FwSizeType dataSize = data.getSize() - dataOffset;
319 
320  // Determine if we can write the whole packet or not
321  if (dataSize <= bytesAvailable) {
322  // Whole packet fits, no need to store to outstanding
323  currentVc.outstanding.offset = 0;
324  } else {
325  // We'll only write a subset (clamp our write to the available size)
326  dataSize = bytesAvailable;
327 
328  // We'll pick up serialization from here later
329  currentVc.outstanding.offset = dataOffset + dataSize;
330  currentVc.outstanding.packet = data;
331  }
332 
333  status = frameSerializer.serializeFrom(dataStart, dataSize, Fw::Serialization::OMIT_LENGTH);
334  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
335 
336  // Shift our offset into the M_PDU payload region by how many bytes we wrote
337  currentVc.current_payload_offset = static_cast<U16>(currentVc.current_payload_offset + dataSize);
338 
339  // Context must always be present once any packet payload bytes are written
340  currentVc.outstanding.context = context;
341 
342  // Return the buffer if no bytes are outstanding
343  if (currentVc.outstanding.offset == 0) {
344  // Return the buffer if this isn't the SPP Idle buff
345  if (!buffer_belongs(data, currentVc.spp_idle.backer, sizeof(currentVc.spp_idle.backer))) {
346  this->dataReturnOut_out(0, data,
347  context); // return ownership of the original data buffer
348  }
349 
350  currentVc.outstanding.packet = {};
351  currentVc.outstanding.offset = 0;
352  }
353 }
354 
355 void AosFramer ::serialize_idle_spp_packet(Fw::SerialBufferBase& serializer, U16 length) {
356  // APID to use for this Idle Packet
357  constexpr U16 idleApid = static_cast<U16>(ComCfg::Apid::SPP_IDLE_PACKET);
358 
359  // Length token is defined as the number of bytes of payload data minus 1
360  const U16 lengthToken = static_cast<U16>(length - SpacePacketHeader::SERIALIZED_SIZE - 1);
361 
362  SpacePacketHeader header;
363  header.set_packetIdentification(idleApid);
364  header.set_packetSequenceControl(
365  0x3 << SpacePacketSubfields::SeqFlagsOffset); // Sequence Flags = 0b11 (unsegmented) & unused Seq count
366  header.set_packetDataLength(lengthToken);
367  // Serialize header into frame
368  Fw::SerializeStatus status = serializer.serializeFrom(header);
369  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
370 
371  // Fill with idle pattern
372  for (U16 i = static_cast<U16>(SpacePacketHeader::SERIALIZED_SIZE); i < length; i++) {
373  status = serializer.serializeFrom(SPP_IDLE_DATA_PATTERN); // Idle data
374  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
375  }
376 }
377 
378 void AosFramer ::fill_with_idle_packet(AosVc& vc, const ComCfg::FrameContext& context) {
379  // Bytes that aren't actual PDUs
380  const U16 overhead = START_OF_PAYLOAD + (m_fecf ? AOSTrailer::SERIALIZED_SIZE : 0);
381  // Bytes for all the PDUs in this VC Frame
382  FW_ASSERT(vc.frame.buffer.getSize() > 0, static_cast<FwAssertArgType>(vc.frame.buffer.getSize()));
383  FW_ASSERT(vc.frame.buffer.getSize() < std::numeric_limits<U16>::max(),
384  static_cast<FwAssertArgType>(vc.frame.buffer.getSize()));
385  const U16 pduSize = static_cast<U16>(vc.frame.buffer.getSize() - overhead);
386 
387  // How many bytes are left over
388  const U16 idlePacketSize = static_cast<U16>(pduSize - vc.current_payload_offset);
389 
390  // Grab a serializer @ the current offset
391  auto frameSerializer = vc.frame.buffer.getSerializer();
392  Fw::SerializeStatus status = frameSerializer.moveSerToOffset(START_OF_PAYLOAD + vc.current_payload_offset);
393  FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
394 
395  // If there wasn't a fresh packet before this
396  // set the M_PDU firstHeaderPointer
397  if (!vc.past_first_fresh_packet) {
398  // setup our headers if we have a packet + context ready to go
399  setup_header(context);
400  setup_m_pdu_header(context);
401 
402  // This idle packet is the fresh packet
403  vc.past_first_fresh_packet = true;
404  }
405 
406  // Use EPP if we can (solves for all sizes)
407  if (vc.idle_packet_types & PvnBitfield::EPP_MASK) {
408  // TODO: Serialize an EPP of the right size
409  FW_ASSERT(1);
410  }
411  // While we are using only SPP, we have to comply w/ the min SPP packet size
412  // We'll stripe this packet onto the next frame of this VC if we have to
413  else if (idlePacketSize < 7) {
414  // Serialize the Idle packet into the spp_idle_backer
415 
416  // Make sure we aren't overwriting a packet fragment
417  FW_ASSERT(!vc.outstanding.packet.isValid());
418 
419  // Setup the PDU to point at our idle buffer
420  vc.outstanding.packet = {vc.spp_idle.backer, MIN_SPP_LENGTH};
421 
422  // Write the SPP Idle to the outstanding packet
423  auto pduSerializer = vc.outstanding.packet.getSerializer();
424  serialize_idle_spp_packet(pduSerializer, MIN_SPP_LENGTH);
425 
426  // Remove the sendNow param
427  // The idle fragment that spills onto the next doesn't need to be sentNow
428  ComCfg::FrameContext filtered_context = context;
429  filtered_context.set_sendNow(false);
430 
431  // Use the normal pack command since we will have leftovers
432  // There won't be an outstanding packet since we'd use those bytes instead
433  pack_packet(vc.outstanding.packet, filtered_context);
434  } else {
435  // Serialize an idle packet right into the frame
436  serialize_idle_spp_packet(frameSerializer, idlePacketSize);
437 
438  // Increment the offset since we serialized directly into the frame
439  vc.current_payload_offset = static_cast<U16>(vc.current_payload_offset + idlePacketSize);
440  }
441 }
442 
443 } // namespace Ccsds
444 } // namespace Svc
void configure(U32 fixedFixedSize, bool frameErrorControlField, U16 spacecraftId=ComCfg::SpacecraftId, U8 vcId=1, U8 idlePvns=PvnBitfield::SPP_MASK)
Definition: AosFramer.cpp:26
Serialization/Deserialization operation was successful.
static U16 compute(const U8 *buffer, U32 length)
compute CRC16 for a buffer
Definition: CRC16.hpp:43
Advanced Orbiting Systems SDL.
Definition: TfvnEnumAc.hpp:45
Representing success.
PlatformSizeType FwSizeType
void dataOut_out(FwIndexType portNum, Fw::Buffer &data, const ComCfg::FrameContext &context) const
Invoke output port dataOut.
~AosFramer()
Destroy AosFramer object.
Definition: AosFramer.cpp:24
U8 * getData() const
Definition: Buffer.cpp:56
Auto-generated base for AosFramer component.
void set_sendNow(bool sendNow)
Set member sendNow.
virtual SerializeStatus serializeFrom(U8 val, Endianness mode=Endianness::BIG)=0
Serialize an 8-bit unsigned integer value.
AosFramer(const char *const compName)
Construct AosFramer object.
Definition: AosFramer.cpp:19
void comStatusOut_out(FwIndexType portNum, Fw::Success &condition) const
Invoke output port comStatusOut.
The size of the serial representation.
SerializeStatus
forward declaration for string
void dataReturnOut_out(FwIndexType portNum, Fw::Buffer &data, const ComCfg::FrameContext &context) const
Invoke output port dataReturnOut.
bool get_sendNow() const
Get member sendNow.
Omit length from serialization.
U8 get_vcId() const
Get member vcId.
bool isConnected_comStatusOut_OutputPort(FwIndexType portNum) const
The size of the serial representation.
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
FwSizeType getSize() const
Definition: Buffer.cpp:60
PlatformIndexType FwIndexType
Type used to pass context info between components during framing/deframing.
RateGroupDivider component implementation.
Per Space Packet Standard, all 1s (11bits) is reserved for Idle Packets.
Definition: ApidEnumAc.hpp:51
The size of the serial representation.
#define FW_ASSERT(...)
Definition: Assert.hpp:14
Success/Failure.
PlatformAssertArgType FwAssertArgType
The type of arguments to assert functions.