A minimal, dependency-free embedded C implementation of the CCSDS File
Delivery Protocol (CFDP) wire format. Part of the OpenSpaceCode initiative —
reusable, standards-aligned components for small-scale space applications.
Standards Compliance
CCSDS 727.0-B-5: CCSDS File Delivery Protocol (CFDP) — Blue Book.
This library implements the CFDP wire format — every PDU of §5, with the LV and TLV parameters of §5.4 — together with the two protocol procedures that operate directly on PDUs and files: the PDU CRC (§4.1) and the file checksums (§4.2).
The transaction procedures (§4.3–§4.12), user operations (§6) and the filestore itself are out of scope, as are timers, retransmission and the transaction state machine.
Features
Core Protocol Implementation
Fixed PDU header (§5.1) — full flag set, variable-length entity IDs and
transaction sequence numbers (1–8 octets), 32- and 64-bit (large file) modes.
File Data PDU (§5.3) — segment offset plus file data, including the
record continuation state and segment metadata when the header’s segment
metadata flag is set.
File Directive PDUs (§5.2) — EOF, Finished, ACK, Metadata, NAK,
Prompt and Keep Alive. The NAK codec carries up to
CFDP_NAK_MAX_SEGMENT_REQUESTS (32 by default, overridable) segment
requests, and rejects a PDU needing more rather than dropping the excess.
LV and TLV parameters (§5.1.8, §5.1.9, §5.4) — filestore requests and
responses, messages to user, fault handler overrides, flow labels and
entity IDs.
Fault Location (§5.2.2, §5.2.3) — encoded and decoded directly by the EOF
and Finished codecs, which refuse to emit a fault condition without it.
File checksums (§4.2) — the mandatory modular (type 0) and null (type 15)
algorithms, streaming and segment-order independent, selectable by the
checksum type carried in the Metadata PDU.
PDU CRC (§4.1) — the CCSDS TC CRC-16 (CCSDS 232.0-B-3 §4.2.1.3),
appended by cfdp_pdu_crc_append() and checked by cfdp_pdu_crc_verify();
cfdp_pdu_payload_size() excludes the trailer from the payload length so the
CRC octets are never decoded as file data or as part of a TLV chain.
Not Implemented
Optional checksum types 1–14 (§4.2.2.5) — reported as unsupported by
cfdp_checksum_type_supported(), so the caller can apply the §4.2.2.8
fallback.
Transaction procedures (§4.3–§4.12) and user operations (§6).
Design Principles
No heap usage — every buffer is caller-supplied.
No external dependencies — C99, standard library headers only.
Round-trip symmetry — every _serialize has a matching _deserialize.
Builds the static library, the example binary and the test binary in build/.
The C standard, include paths and warning set are fixed in the Makefile and apply to the
library, example and tests alike. Only the optimisation/instrumentation flags are meant to be
overridden, via OPT:
make OPT="-O0 -g"
Build Library Only
make lib
# Produces: build/lib/libcfdp.a
Build and Run the Example
make example
./build/examples/example
Run Tests
make run
Coverage (HTML)
Requires gcovr:
pip install gcovr
make coverage-html
# Prints a line/branch summary
# Output: build/coverage/index.html
The script rebuilds with OPT="-O0 -g --coverage", so instrumentation is the only difference
from a normal build.
Clean
make clean
Quick Start
Step 1 — Serialise a header and an EOF PDU, with a CRC
Leave crc_flag clear, size data_field_length for the payload alone and skip
cfdp_pdu_crc_append() to send without a CRC.
Step 2 — Parse a received PDU
if (!cfdp_pdu_crc_verify(rx, rx_len)) {
/* §4.1.2: discard. Passes any PDU whose CRC flag is clear. */
return;
}
cfdp_pdu_header_t hdr;
size_t hlen = cfdp_pdu_header_deserialize(rx, rx_len, &hdr);
const uint8_t *payload = rx + hlen;
/* The data field length counts the CRC when the CRC flag is set (§4.1.3.2),
* so never pass hdr.data_field_length straight to a payload codec. */
size_t payload_len = cfdp_pdu_payload_size(&hdr);
if (hdr.pdu_type == CFDP_PDU_TYPE_DIRECTIVE) {
cfdp_directive_code_t code;
cfdp_pdu_directive_code(payload, payload_len, &code);
/* dispatch on `code` (CFDP_DIRECTIVE_EOF, ...) */
} else {
cfdp_file_data_pdu_t fd;
cfdp_file_data_deserialize(payload, payload_len, hdr.large_file_flag,
hdr.segment_metadata_flag, &fd);
/* write fd.file_data_len octets at fd.offset */
}
cfdp_pdu_crc_append() expects the header to have been serialised with the CRC
flag set and data_field_length already counting the two CRC octets, and
refuses otherwise — forgetting the + CFDP_PDU_CRC_LEN is the mistake it
exists to catch. cfdp_pdu_crc_verify() implements §4.1.2 as a single
accept/discard decision and ignores octets beyond the PDU the header describes,
so a padded link frame can be handed to it directly.
Both take the header’s large-file and segment-metadata flags, which select the
data field layout (§5.3). Pass the same values carried in the header actually
sent or received: the serialiser rejects a payload whose segment metadata
disagrees with the flag, since that mismatch would silently shift the offset
field at the peer.
Directives (cfdp_directive.h)
cfdp_{eof,finished,ack,metadata,nak,prompt,keep_alive}_{serialize,deserialize}()
— each returns the number of octets written or consumed, or 0 on error.
Pass cfdp_pdu_payload_size(&hdr) as the data field length when decoding, not
hdr.data_field_length, so a trailing CRC is not parsed as PDU content.
The ACK codec accepts only EOF and Finished as the acknowledged directive, the
only two table 5-8 allows, and derives the directive subtype code from it —
0001 for Finished, 0000 for EOF — so cfdp_ack_pdu_t has no subtype field
to set wrongly. A received ACK of any other directive, or with a subtype that
disagrees with its directive code, is rejected.
cfdp_nak_deserialize() rejects a data field whose segment requests do not fill
it exactly, and one carrying more requests than cfdp_nak_pdu_t can hold —
truncating would tell the sender that gaps it never saw had been satisfied, so
they would never be retransmitted. Raise CFDP_NAK_MAX_SEGMENT_REQUESTS (it
sizes the struct: 32 requests is 536 octets on a 64-bit build) on links where the receiver
routinely reports more gaps than the default.
Each has a matching _deserialize(). Metadata options and Finished filestore
responses are carried as pre-encoded TLV chains: build one with these codecs,
point the PDU struct at it, and walk a received chain by advancing through
cfdp_tlv_deserialize() by its return value.
cfdp_checksum_update/compute implement the modular checksum directly.
cfdp_checksum_update_by_type applies whichever algorithm a Metadata PDU
names: the modular or the null checksum, or false for a type this library does
not implement. §4.2.2.8 then calls for an Unsupported Checksum Type fault, with
the sender falling back to the modular checksum and the receiver to the null
checksum; that choice depends on the entity’s role and is left to the caller.
The raw CCSDS TC CRC-16 (polynomial 0x1021, preset 0xFFFF, no final
inversion; "123456789" → 0x29B1), for callers that assemble PDUs in pieces.
The PDU-level helpers above are the normal entry points.
Return-value convention
Every _serialize / _deserialize function returns the number of octets
written or consumed, and 0 on any error (NULL argument, buffer too small, or
malformed input).
Memory Usage (Estimated)
Library (stripped): a few kilobytes of .text; no static state.
No heap usage: all allocations are caller-supplied.
Serialization buffers: caller-sized; a full PDU header is at most
CFDP_PDU_HEADER_MAX_LEN (28) octets.
Limitations
No transaction state machine, timers, retransmission or filestore procedures
(§4.3–§4.12): the library encodes and decodes PDUs and leaves the protocol
behaviour to the caller.
The EOF and Finished decoders do not enforce the Fault Location rule their
encoders do: a fault condition without the TLV, or the TLV without a fault
condition, still decodes. Check fault_location_len if it matters.
Reserved and spare bits are not checked on decode; a PDU with them set
decodes as if they were clear.
Filestore Response status codes are not validated against the per-action
ranges of table 5-18; the action code itself is.
Checksum types 1–14 are not implemented (see Not Implemented above).
References
CCSDS 727.0-B-5, CCSDS File Delivery Protocol (CFDP), Blue Book.
CCSDS 720.1-G-4, CFDP — Part 1: Introduction and Overview, Green Book.
EmbeddedCFDP
A minimal, dependency-free embedded C implementation of the CCSDS File Delivery Protocol (CFDP) wire format. Part of the OpenSpaceCode initiative — reusable, standards-aligned components for small-scale space applications.
Standards Compliance
This library implements the CFDP wire format — every PDU of §5, with the LV and TLV parameters of §5.4 — together with the two protocol procedures that operate directly on PDUs and files: the PDU CRC (§4.1) and the file checksums (§4.2). The transaction procedures (§4.3–§4.12), user operations (§6) and the filestore itself are out of scope, as are timers, retransmission and the transaction state machine.
Features
Core Protocol Implementation
CFDP_NAK_MAX_SEGMENT_REQUESTS(32 by default, overridable) segment requests, and rejects a PDU needing more rather than dropping the excess.cfdp_pdu_crc_append()and checked bycfdp_pdu_crc_verify();cfdp_pdu_payload_size()excludes the trailer from the payload length so the CRC octets are never decoded as file data or as part of a TLV chain.Not Implemented
cfdp_checksum_type_supported(), so the caller can apply the §4.2.2.8 fallback.Design Principles
_serializehas a matching_deserialize.Project Structure
Building
Build Everything
Builds the static library, the example binary and the test binary in
build/.The C standard, include paths and warning set are fixed in the
Makefileand apply to the library, example and tests alike. Only the optimisation/instrumentation flags are meant to be overridden, viaOPT:Build Library Only
Build and Run the Example
Run Tests
Coverage (HTML)
Requires
gcovr:The script rebuilds with
OPT="-O0 -g --coverage", so instrumentation is the only difference from a normal build.Clean
Quick Start
Step 1 — Serialise a header and an EOF PDU, with a CRC
Leave
crc_flagclear, sizedata_field_lengthfor the payload alone and skipcfdp_pdu_crc_append()to send without a CRC.Step 2 — Parse a received PDU
API Reference
PDU header (
cfdp_pdu.h)cfdp_pdu_crc_append()expects the header to have been serialised with the CRC flag set anddata_field_lengthalready counting the two CRC octets, and refuses otherwise — forgetting the+ CFDP_PDU_CRC_LENis the mistake it exists to catch.cfdp_pdu_crc_verify()implements §4.1.2 as a single accept/discard decision and ignores octets beyond the PDU the header describes, so a padded link frame can be handed to it directly.File Data (
cfdp_pdu.h)Both take the header’s large-file and segment-metadata flags, which select the data field layout (§5.3). Pass the same values carried in the header actually sent or received: the serialiser rejects a payload whose segment metadata disagrees with the flag, since that mismatch would silently shift the offset field at the peer.
Directives (
cfdp_directive.h)cfdp_{eof,finished,ack,metadata,nak,prompt,keep_alive}_{serialize,deserialize}()— each returns the number of octets written or consumed, or0on error.Pass
cfdp_pdu_payload_size(&hdr)as the data field length when decoding, nothdr.data_field_length, so a trailing CRC is not parsed as PDU content.The ACK codec accepts only EOF and Finished as the acknowledged directive, the only two table 5-8 allows, and derives the directive subtype code from it —
0001for Finished,0000for EOF — socfdp_ack_pdu_thas no subtype field to set wrongly. A received ACK of any other directive, or with a subtype that disagrees with its directive code, is rejected.cfdp_nak_deserialize()rejects a data field whose segment requests do not fill it exactly, and one carrying more requests thancfdp_nak_pdu_tcan hold — truncating would tell the sender that gaps it never saw had been satisfied, so they would never be retransmitted. RaiseCFDP_NAK_MAX_SEGMENT_REQUESTS(it sizes the struct: 32 requests is 536 octets on a 64-bit build) on links where the receiver routinely reports more gaps than the default.LV/TLV parameters (
cfdp_tlv.h)Each has a matching
_deserialize(). Metadata options and Finished filestore responses are carried as pre-encoded TLV chains: build one with these codecs, point the PDU struct at it, and walk a received chain by advancing throughcfdp_tlv_deserialize()by its return value.Checksum (
cfdp_checksum.h)cfdp_checksum_update/computeimplement the modular checksum directly.cfdp_checksum_update_by_typeapplies whichever algorithm a Metadata PDU names: the modular or the null checksum, orfalsefor a type this library does not implement. §4.2.2.8 then calls for an Unsupported Checksum Type fault, with the sender falling back to the modular checksum and the receiver to the null checksum; that choice depends on the entity’s role and is left to the caller.CRC (
cfdp_crc.h)The raw CCSDS TC CRC-16 (polynomial
0x1021, preset0xFFFF, no final inversion;"123456789"→0x29B1), for callers that assemble PDUs in pieces. The PDU-level helpers above are the normal entry points.Return-value convention
Every
_serialize/_deserializefunction returns the number of octets written or consumed, and0on any error (NULL argument, buffer too small, or malformed input).Memory Usage (Estimated)
.text; no static state.CFDP_PDU_HEADER_MAX_LEN(28) octets.Limitations
fault_location_lenif it matters.References
License
See LICENSE file.