Skip to content

File ccsds_tm_frame.h

FileList > ccsds_tm > ccsds_tm_frame.h

Go to the source code of this file

The CCSDS frame assembler — where the ASM goes, and the one place the stages' disagreements about what they cover become visible. More...

  • #include "ccsds_tm/ccsds_tm.h"
  • #include "ccsds_tm/ccsds_tm_rs.h"
  • #include "wfm/wfm_frame.h"
  • #include <stddef.h>
  • #include <stdint.h>

Classes

Type Name
struct ccsds_tm_frame_cfg_t
Which coding is applied to one Transfer Frame.
struct ccsds_tm_frame_layout_t
The shape of one CADU, and what each stage covered.
struct ccsds_tm_frame_rx_t
What ccsds_tm_frame_decode found on the way through.
struct ccsds_tm_frame_span_t
A run of CADU bits, as a half-open range [first, first + n) .
struct ccsds_tm_frame_spec_t
A framed waveform's choices, before they become a description.

Public Functions

Type Name
size_t ccsds_tm_frame_decode (const ccsds_tm_frame_cfg_t * cfg, const uint8_t * cadu, size_t n_cadu, uint8_t * frame, size_t max_frame, ccsds_tm_frame_rx_t * rx)
Recover a Transfer Frame from the bits of one CADU.
int ccsds_tm_frame_desc_of (const ccsds_tm_frame_spec_t * s, wfm_frame_desc_t * d)
Turn those choices into a description: fields, stages, covers.
int ccsds_tm_frame_describe (const ccsds_tm_frame_cfg_t * cfg, size_t frame_len, const uint8_t * frame_bits, wfm_frame_desc_t * out)
This CADU as a wfm_frame_desc_t — the standard as DATA.
size_t ccsds_tm_frame_encode (const ccsds_tm_frame_cfg_t * cfg, conv_enc_t * conv, const uint8_t * frame, size_t frame_len, uint8_t * out, size_t max_out)
Encode one Transfer Frame into channel symbols.
size_t ccsds_tm_frame_layout (const ccsds_tm_frame_cfg_t * cfg, size_t frame_len, ccsds_tm_frame_layout_t * out)
Work out the CADU shape for a config, without encoding anything.
void ccsds_tm_frame_ops (wfm_frame_ops_t * out, conv_enc_t * conv)
The kernels a described CADU is assembled with.

Detailed Description

The four kernels in ccsds_tm/ are each separately falsifiable against a published value, and each of them is right on its own. What none of them can be wrong about alone is the thing this file exists for: the stages do not all cover the same bits.

transfer frame                      223 * I octets
  -> R-S (255,223) E=16, depth I    4.3, 4.4.1  -> codeblock
  -> pseudo-randomiser              10.3.2      -> randomised codeblock
  -> ASM 0x1ACFFC1D prepended       9.4.1       -> CADU (table 9-1)
  -> convolutional K=7, r=1/2       3.2.1       -> channel symbols

Read as a pipeline that is four stages long and correct. Read as what each stage covers it is not, because the marker enters third and one of the two stages after it reaches back over it:

stage covers the ASM 131.0-B-3
Reed-Solomon (outer) no 9.5.1, 9.2.1.5
pseudo-randomiser no 10.3.2, 10.3.4 n.1
convolutional (inner) yes 3.2.1, 9.2.1.4

9.2.1.5 states both halves in one sentence — *"the ASM shall be encoded by the inner code but not by the outer code"* — and 10.3.4's first NOTE states the third outright: *"The ASM was not randomized and is not derandomized."*

That is why ccsds_tm_frame_layout_t reports a span per stage rather than an order. An order is the representation that cannot express this: any chain of optional transforms applied to "the frame" is right at three stage boundaries and wrong at the fourth, and wrong in the direction that still encodes, still decodes against itself, and syncs to nothing. A span makes the disagreement a value a test can assert, which is what test_ccsds_tm_frame does against all three rows above.

The packed/unpacked boundary lives here

ccsds_tm_rs.h takes packed symbols, because a Reed-Solomon symbol is a byte; ccsds_tm.h takes unpacked bits, one per byte, because a randomiser and a convolutional coder are bit machines. Both are right, and the conversion between them belongs to exactly one place rather than being hidden inside a kernel that then only works for one caller.

This is that place: ccsds_tm_frame_encode takes a Transfer Frame as packed octets and returns unpacked channel symbols, the representation wfm_frame_bits and the spreader already pass around. Octets go on the wire MSB-first — figure 9-1 numbers the first transmitted bit of the ASM as the most significant bit of 0x1A, and 4.3.9.2 orders an R-S symbol the same way.

What is not here

Virtual fill (4.4.2's shortened codeblock) is not implemented, so a frame whose length is not exactly 223 * I octets is refused rather than padded. Silently padding would produce a codeblock a receiver configured for the full length cannot parse, which is the failure this whole slice is built to avoid.

See also: ccsds_tm.h for the ASM pattern, the randomiser and the inner code.

See also: ccsds_tm_rs.h for the outer code and the interleaver.

Public Functions Documentation

function ccsds_tm_frame_decode

Recover a Transfer Frame from the bits of one CADU.

size_t ccsds_tm_frame_decode (
    const ccsds_tm_frame_cfg_t * cfg,
    const uint8_t * cadu,
    size_t n_cadu,
    uint8_t * frame,
    size_t max_frame,
    ccsds_tm_frame_rx_t * rx
) 

The mirror of ccsds_tm_frame_encode, over the same spans and reading the same ccsds_tm_frame_cfg_t — so the two cannot disagree about which stage covered what, which is the failure ccsds_tm_frame.h opens by describing.

Where the inner code is, and why it is not here

This begins after the inner decode and after frame synchronisation: cadu is one marker-plus-codeblock, already Viterbi-decoded and already aligned by ccsds_tm_asm_find. That is not an omission, it is the only place the boundary can go. A Viterbi is streaming and emits its decisions depth bits late, so the bits of one CADU are not a function of that CADU's symbols alone; and the marker that says where a CADU starts is only readable once the inner code has been undone. A function taking channel symbols would therefore have to own a decoder, a search window and a buffer — that is a streaming receiver object, and this is the pure per-frame chain it would call.

Consistent with the encoder, where conv_enc_t belongs to the caller for the same reason: the inner code is continuous and the frame is not.

What it undoes

The marker is skipped, the randomiser is re-applied over the block span (10.3.4 — it is involutive, so the same call serves both directions), the block is packed back to octets MSB-first, and with an outer code each of the rs_depth interleaved codewords is decoded — up to E = 16 symbol errors per codeword repaired, in place, before anything reads the frame. The Transfer Frame is the information section, which 4.4.1 keeps in the order it entered.

Parameters:

  • cfg The coding that was applied. Must match the transmitter.
  • cadu n_cadu unpacked CADU bits, one per byte.
  • n_cadu Number of CADU bits; must equal the layout's cadu_bits for this configuration.
  • frame Receives the recovered Transfer Frame, packed octets.
  • max_frame Capacity of frame in octets.
  • rx Receives what was found; may be NULL.

Returns:

Transfer Frame octets written, or 0 if the configuration is refused, n_cadu is not the layout's CADU length, or max_frame is too small — in which case frame is untouched.

const ccsds_tm_frame_cfg_t cfg
    = { .rs_depth = 5, .randomise = 1, .attach_asm = 1,
        .convolutional = 1 };
ccsds_tm_frame_layout_t lay;
ccsds_tm_frame_layout (&cfg, 223 * 5, &lay);

uint8_t        frame[223 * 5];
ccsds_tm_frame_rx_t rx;
// `cadu` is lay.cadu_bits of Viterbi output, ASM-aligned.
const size_t n = ccsds_tm_frame_decode (&cfg, cadu, lay.cadu_bits, frame,
                                   sizeof frame, &rx);
printf ("%zu octets, R-S %u/%u ok, %u symbols repaired\n", n, rx.rs_ok,
        rx.rs_codewords, rx.rs_symbols);

function ccsds_tm_frame_desc_of

Turn those choices into a description: fields, stages, covers.

int ccsds_tm_frame_desc_of (
    const ccsds_tm_frame_spec_t * s,
    wfm_frame_desc_t * d
) 

The ONE place this standard's coverage table becomes data, so a generator and whatever undoes the frame later hold the same layout rather than each deriving one.

Parameters:

  • s the choices.
  • d receives the description.

Returns:

0, or -1 on NULL, or if the geometry needs more fields or stages than a description holds.


function ccsds_tm_frame_describe

This CADU as a wfm_frame_desc_t — the standard as DATA.

int ccsds_tm_frame_describe (
    const ccsds_tm_frame_cfg_t * cfg,
    size_t frame_len,
    const uint8_t * frame_bits,
    wfm_frame_desc_t * out
) 

The same fact CCSDS_TM_CONV states about the inner code and CCSDS_TM_RS about the outer one, at the level of the frame: 131.0-B-3 section 9 is a CONFIGURATION of a general description, not a framer of its own. Three fields — the marker, the Transfer Frame, and the check symbols the outer code derives — and three stages whose covers are the whole of the coverage table this file opens with.

The dependency runs THIS way on purpose. wfm/wfm_frame.h knows nothing about CCSDS; if it called this component's kernels the two would form a cycle, so the kernels travel as ccsds_tm_frame_ops instead.

Parameters:

  • cfg the coding to apply.
  • frame_len Transfer Frame length in octets.
  • frame_bits frame_len * 8 unpacked Transfer Frame bits, MSB-first — the representation the description works in, so the packed/unpacked boundary is crossed by the caller and is visible rather than hidden in a kernel. May be NULL to describe the geometry alone.
  • out receives the description.

Returns:

0, or -1 if the configuration is refused — the same refusals ccsds_tm_frame_layout applies, for the same reasons.


function ccsds_tm_frame_encode

Encode one Transfer Frame into channel symbols.

size_t ccsds_tm_frame_encode (
    const ccsds_tm_frame_cfg_t * cfg,
    conv_enc_t * conv,
    const uint8_t * frame,
    size_t frame_len,
    uint8_t * out,
    size_t max_out
) 

Runs whichever of the four stages cfg selects, each over the bits it covers and no others.

The inner encoder belongs to the CALLER, because it is continuous

3.3.2 fixes the output as one uninterrupted symbol sequence with no per-frame flush, so the register carries from the last bit of one CADU into the first bit of the next. conv is where it lives. Pass the same one to every call in a stream; pass NULL for a frame encoded on its own.

The difference is small and it is exactly where it hurts: measured on depth 1, encoding two frames with NULL differs from the continuous stream in 6 of 8288 symbols, all of them in the first 7 symbols of frame 2 — the K - 1 = 6 bits of register memory, landing on the ASM a receiver is trying to correlate. A matched Viterbi absorbs it, which is what makes this the same class as the inversion on G2 and the dual basis: self-consistent, decodes against a receiver of one's own construction, and not what the standard says.

Parameters:

  • cfg The coding to apply.
  • conv Inner-encoder state (conv_enc_t) carried across frames, or NULL to start from the all-zero register. Ignored when cfg->convolutional is 0.
  • frame frame_len packed octets, MSB-first on the wire.
  • frame_len Transfer Frame length in octets.
  • out Receives the unpacked channel symbols, one per byte.
  • max_out Capacity of out in symbols. The CADU is assembled in the TAIL of this buffer, so a short one is not a truncated result but a write past the end — hence a capacity rather than a comment telling you to call ccsds_tm_frame_layout first.

Returns:

The number of symbols written, or 0 if the configuration is refused or max_out is too small — in which case out is untouched.

uint8_t frame[223 * 5];
uint8_t sym[(32 + 255 * 5 * 8) * 2];
const ccsds_tm_frame_cfg_t cfg
    = { .rs_depth = 5, .randomise = 1, .attach_asm = 1,
        .convolutional = 1 };
conv_enc_t conv;
conv_enc_init (&conv);
const size_t n
    = ccsds_tm_frame_encode (&cfg, &conv, frame, sizeof frame, sym,
                        sizeof sym);

function ccsds_tm_frame_layout

Work out the CADU shape for a config, without encoding anything.

size_t ccsds_tm_frame_layout (
    const ccsds_tm_frame_cfg_t * cfg,
    size_t frame_len,
    ccsds_tm_frame_layout_t * out
) 

This is both the buffer-sizing call and the description of the coverage the encoder will apply, which is deliberate: a caller that sizes its buffer from one function and reasons about coverage from a comment is a caller whose two beliefs can drift apart.

Parameters:

  • cfg The coding to apply.
  • frame_len Transfer Frame length in octets.
  • out Receives the layout; may be NULL to ask only for the output length.

Returns:

The number of channel symbols the encode will write, or 0 if the configuration is refused — an interleaving depth outside 4.3.5.1's {1, 2, 3, 4, 5, 8}, an empty frame, or a frame that is not exactly CCSDS_TM_RS_K * rs_depth octets when the outer code is in use.

const ccsds_tm_frame_cfg_t cfg
    = { .rs_depth = 5, .randomise = 1, .attach_asm = 1,
        .convolutional = 1 };
const size_t n = ccsds_tm_frame_layout (&cfg, 223 * 5, NULL);
uint8_t *sym = malloc (n);          // n == (32 + 255 * 5 * 8) * 2

function ccsds_tm_frame_ops

The kernels a described CADU is assembled with.

void ccsds_tm_frame_ops (
    wfm_frame_ops_t * out,
    conv_enc_t * conv
) 

The outer code, the randomiser and the inner code, as the transforms wfm_frame_assemble calls. Each one is the same function ccsds_tm_frame_encode calls, so the two paths cannot come to disagree about what a stage does — only about which bits it is handed, and that is what the description states.

Parameters:

  • out receives the table.
  • conv inner-encoder state carried across frames, or NULL to start each frame from the all-zero register. Exactly ccsds_tm_frame_encode's conv, and it matters for the same reason: 3.3.2 fixes one uninterrupted symbol sequence.


The documentation for this class was generated from the following file native/inc/ccsds_tm/ccsds_tm_frame.h