Implement a Framing Protocol
This How-To Guide provides a step-by-step guide to implementing a custom framing protocol in F Prime Flight Software.
Modern F´ deployments use the CCSDS protocol by default via the Svc.ComCcsds subtopology. The lightweight F Prime Protocol (available via Svc.ComFprime) is also available as an alternative low-overhead communications protocol that the F Prime GDS understands. However, some projects choose to implement another framing protocol that fits their mission requirements. This document provides an overview of how to implement a custom framing protocol in F Prime Flight Software, and how to integrate it with the F Prime GDS.
Overview
This guide demonstrates how to implement a custom framing protocol, referred to here as MyCustomProtocol. The protocol defines how data is wrapped (framed) for transmission and how frames are validated and unpacked (deframed) on reception. For a bi-directional framing implementation (uplink and downlink), you will need to implement both a framer and a deframer component. A framer is required for downlink (flight software → GDS). A deframer (and optionally a FrameDetector) is needed for deframing uplink messages (GDS → flight software).
A reference implementation of a custom framing protocol (the "Decaf Protocol") is available in the fprime-examples repository:
- C++ CustomFraming Example
- GDS Plugin Example
This guide is divided into two main sections: flight software implementation and GDS integration. If you are aiming to integrate with another GDS and do not wish to use the F´ GDS, you can skip the GDS section.
Note
When using the reference examples, it is recommended to check out the fprime-examples repository at the same release tag as your F´ core installation to ensure compatibility. You can find which version of F´ you are using with fprime-util version-check.
Prerequisites: Understanding Subtopologies
Important: Before implementing custom framing protocols, you should familiarize yourself with F´ subtopologies. Modern F´ deployments ship with the Svc.ComCcsds subtopology by default, which provides a standard communication stack including framing/deframing components.
To implement custom framing protocols, you will typically need to:
1. Understand the existing communication subtopology: Review how the default Svc.ComCcsds subtopology is structured and integrated into your deployment
2. Manually import components: Copy the relevant topology code from the subtopology into your main topology so you can modify the component connections as needed, and remove the old import Svc.ComCcsds statement.
3. Replace standard components: Substitute the default framer/deframer components with your custom implementations
For more information on working with subtopologies, see the Subtopologies Guide. This understanding is essential before proceeding with custom framing implementation.
Flight Software Implementation
To implement a custom framing protocol in F´, will need to implement the following: - Framer: An F´ component that wraps payload data into frames for transmission. - Deframer: An F´ component that unpacks received frames, extracts the payload data, and validates the frame. - FrameDetector Helper Class (optional): A C++ helper class (not an F´ component) that detects the start and end of frames in a stream of data, if your transport does not guarantee complete packets (e.g., TCP, UART).
Tip
When creating framer/deframer components using fprime-util new --component, the recommended settings are to use passive components with events enabled.
The following examples will walk through the implementation of a custom framer and deframer for a hypothetical MyCustomProtocol protocol. Implementation details are left to the reader, but examples of such implementations can be found in the fprime-examples repository, or within the F´ codebase itself (Svc.FprimeFramer and Svc.FprimeDeframer).
Pitfalls and Best Practices
Before implementing, consider these best practices:
-
Centralize Protocol Constants: Define protocol constants (e.g., start word, header structure, field sizes) in a dedicated
.fpptypes file (e.g.,Types/Types.fpp). This allows constants like start words, field types, or spacecraft IDs to be reused consistently across your framer, deframer, and frame detector. -
Endianness Convention: F´ serializes all integer types in big-endian by default (network byte order), as defined in
Fw::Serializable. If your protocol requires little-endian fields, you must specify so during serialization with theFw::Endianness::LITTLEmode, or manually order bytes as needed. Payload serialization and deserialization target theFw::SerialBufferBaseinterface, so user-defined serializable types can useserializeTo/deserializeFromwith a compatible buffer. -
Set the APID in the Deframer: Your deframer must extract and set the APID (Application ID) in the
FrameContextbefore emitting packets downstream viadataOut. Without this, theSvc.FprimeRouterwill not know where to route packets to. This is a critical requirement, unless you are also implementing a custom router and define your own requirements (not recommended and outside the scope of this guide). -
Payload vs. Frame Structure: A framing protocol should only define the outer frame structure (headers and trailers). The internal payload structure is determined by the upper-layer protocols and applications that produce and consume the data, not by the framing protocol itself.
Implementation Steps
- Define the Framer and Deframer FPP Components
Framer and Deframer components should implement the FPP interfaces by including them.
In MyCustomFramer.fpp:
MyCustomDeframer.fpp:
@ Deframer implementation for MyCustomProtocol
passive component MyCustomDeframer {
import Svc.Deframer
[...]
}
- Implement the Framer C++ Component
Implement the required handler functions:
// ...existing code...
void MyCustomFramer ::dataIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context) {
// TODO: Implement framing logic
}
void MyCustomFramer ::comStatusIn_handler(FwIndexType portNum, Fw::Success& condition) {
this->comStatusOut_out(portNum, condition); // pass comStatus through (unless project requires otherwise)
}
void MyCustomFramer ::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context) {
// TODO: handle return of data ownership
// For example, if component required to allocate from a buffer manager, return the buffer to the manager
}
// ...existing code...
- Implement the Deframer C++ Component
Similarly, implement the required handler functions:
// ...existing code...
void MyCustomDeframer ::dataIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context) {
// TODO: Implement deframing logic
}
void MyCustomDeframer ::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context) {
// TODO: handle return of data ownership
}
// ...existing code...
-
Integrate Components into Your Topology
After implementing the framer, deframer, and optionally the frame detector, integrate these components into your main topology. This typically involves: - Removing the import statement for the existing communication subtopology (e.g.,
import Svc.ComCcsds). - Manually adding the topology code from the subtopology as needed. See ComFprime.fpp for a reference code to add to your topology. You should be copy-pasting most of this code into your own topology, and adapt the C++ phase code as necessary. - Updating theframeranddeframerinstances to use your custom implementations.Example snippet from
Top/Topology.fpp:// Remove or comment out the existing subtopology import // import Svc.ComCcsds // Manually add topology code from the subtopology as needed // ... // Replace instances of custom framer and deframer instance framer: MyCustomFramer base id 0x1000 instance deframer: MyCustomDeframer base id 0x2000Troubleshooting Common Integration Issues:
After removing the CCSDS subtopology and adding your custom framing components, you may encounter compilation errors:
- Missing header errors (e.g.,
fatal error: Svc/Subtopologies/ComCcsds/ComCcsdsConfig/FppConstantsAc.hpp: No such file or directory): - Remove all mentions of
ComCcsdsfrom your auto-generated<DeploymentName>TopologyDefs.hppfile (located in<deployment>/Top/). -
After significant topology changes, you may need to do a clean rebuild with
fprime-util generate --force -
Undeclared port enum errors (e.g.,
error: 'Ports_ComPacketQueue' has not been declared): -
Include the generated port headers in your
<DeploymentName>TopologyDefs.hpp: -
Linker errors (e.g.,
undefined reference to 'vtable for CustomFraming::MyCustomFrameDetector'): - Ensure your custom Detector is added as a dependency of your Deframer or deployment, using the
DEPENDSkeyword. In your Deframer'sCMakeLists.txt, add:
- Missing header errors (e.g.,
-
(Optional) Implement a Frame Detector
When is this not needed?
If your communications manager component always receives complete frames, you do not need to implement frame detection. This can be the case when using:
- Radios with built-in frame synchronization
- Message-oriented transport-layer protocols where frame boundaries are guaranteed (e.g. UDP)
When is this needed?
If your data transport is stream-based and does not preserve message boundaries, you must implement a mechanism to delimit frames. Examples include:
- TCP connections (stream-based, no inherent message boundaries)
- UART/serial connections (e.g. UART radios such as XBee)
F Prime provides this capability with the Svc.FrameAccumulator component, which uses a circular buffer and a helper FrameDetector to identify complete frames in the data stream.
Note
The FrameDetector is a C++ helper class, not an FPP component. It does not have an .fpp definition and is used internally by Svc.FrameAccumulator.
To use the Svc.FrameAccumulator, you need to configure it with a FrameDetector that detects when a frame is present:
MyCustomFrameDetector.hpp
#include <Svc/FrameDetector/FrameDetector.hpp>
class MyCustomFrameDetector : public Svc::FrameDetector {
public:
Svc::FrameDetector::Status detect(const Types::CircularBuffer& data, FwSizeType& size_out) const override;
};
MyCustomFrameDetector.cpp
// ...existing code...
Svc::FrameDetector::Status MyCustomFrameDetector::detect(const Types::CircularBuffer& data, FwSizeType& size_out) const {
// TODO: Implement frame boundary detection
// This can include searching for start words, validating headers, checking lengths, checking CRC and hashes, etc.
// Utilities exist for CRC under Utils/Hash, and examples are shown in Svc/Ccsds/Utils or in fprime-examples repo
// Refer to the Svc.FrameDetector documentation for details on how to implement this
return Svc::FrameDetector::NO_FRAME_DETECTED;
}
// ...existing code...
Then configure the Svc.FrameAccumulator component to use your custom frame detector in your Topology CPP:
Top/Topology.cpp
cpp
#include <path/to/MyCustomFrameDetector.hpp>
// ...existing code...
MyCustomFrameDetector frameDetector;
// ...existing code...
frameAccumulator.configure(frameDetector, 1, mallocator, 2048);
F´ GDS Implementation
To support your custom protocol in the F´ GDS, implement a GDS framing plugin. The GDS plugin system allows you to customize GDS behavior with user-provided code. For new framing protocols, you will need to implement a plugin that extends the FramerDeframer. This is further documented in the How-To Develop a GDS Plugin Guide and F Prime GDS Framing Plugin reference.
For example, in Python:
from fprime_gds.common.communication.framing import FramerDeframer
from fprime_gds.plugin.definitions import gds_plugin
@gds_plugin(FramerDeframer)
class MyCustomFramerDeframer(FramerDeframer):
"""GDS plugin for MyCustomProtocol framing"""
def frame(self, data):
# TODO: Implement framing logic
return frame
def deframe(self, data, no_copy=False):
# TODO: Implement deframing logic
return packet, leftover_data, discarded_data
def get_name(self):
# TODO: Return the protocol name for selection with `fprime-gds --framing-selection <selection>`
return "MyCustomProtocol"
Make sure to package and install the plugin in your virtual environment for the GDS to be able to load it, then run it: