File wfm_frame.h¶
FileList > inc > wfm > wfm_frame.h
Go to the source code of this file
A frame's BIT layout, described once and read from both ends. More...
#include <stddef.h>#include <stdint.h>
Classes¶
| Type | Name |
|---|---|
| struct | wfm_field_t One field of a frame — a run of bits that appears on the wire. |
| struct | wfm_frame_desc_layout_t Where every field and every stage landed. |
| struct | wfm_frame_desc_t A frame as a description: what is on the wire, and what covers it. |
| struct | wfm_frame_layout_t Where each field lands, in bits from the start of the frame. |
| struct | wfm_frame_ops_t The kernels an assembly runs, and whatever state they carry. |
| struct | wfm_frame_rx_t What wfm_frame_check found, stage by stage. |
| struct | wfm_frame_span_t A run of bits inside the assembled frame, [first, first + n) . |
| struct | wfm_frame_stage_rx_t What undoing one stage found. |
| struct | wfm_frame_t A frame's bit layout: [preamble × reps | sync | payload | crc] . |
| struct | wfm_seq_t A run of bits, however it is produced. |
| struct | wfm_stage_op_t How one kind of stage actually transforms bits. |
| struct | wfm_stage_t One transform, and — the whole point — the fields it covers. |
Public Types¶
| Type | Name |
|---|---|
| enum | wfm__frame_8h_1a385c44f6fb256e5716a2302a5b940388 Field indices wfm_frame_describe writes, in wire order. |
| enum | wfm_seq_kind_t Where a run of bits comes from. |
| enum | wfm_stage_kind_t Stage kinds doppler itself names. |
Public Functions¶
| Type | Name |
|---|---|
| size_t | wfm_dsss_desc_chips (const wfm_frame_desc_t * d, const wfm_frame_ops_t * ops, const uint8_t * acq_code, size_t acq_len, size_t acq_reps, const uint8_t * data_code, size_t data_len, uint8_t * out, size_t max_out) Build a two-code DSSS burst from a description: assemble, spread. |
| size_t | wfm_dsss_desc_nchips (const wfm_frame_desc_t * d, size_t acq_len, size_t acq_reps, size_t data_len) Chip count of a DSSS burst built from a description. |
| int | wfm_frame_add_derived (wfm_frame_desc_t * d, const char * name, size_t bits) Append a named DERIVED field — one a stage will fill. Returns its index, or -1. |
| int | wfm_frame_add_field (wfm_frame_desc_t * d, const char * name, const wfm_seq_t * seq, size_t reps) Append a named field. Returns its index, or -1. |
| int | wfm_frame_add_stage (wfm_frame_desc_t * d, uint32_t kind, const char * first, const char * last) Append a stage covering [first .. last] BY NAME. Returns its index, or -1. |
| size_t | wfm_frame_assemble (const wfm_frame_desc_t * d, const wfm_frame_ops_t * ops, uint8_t * out, size_t max_out) Materialise a description: run every field, then every stage. |
| size_t | wfm_frame_bits (const wfm_frame_t * f, uint8_t * out, size_t max_out) Materialise the frame as one flat 0/1 bit array. |
| int | wfm_frame_check (const wfm_frame_desc_t * d, const wfm_frame_ops_t * ops, uint8_t * bits, wfm_frame_rx_t * rx) Undo a description's stages over a received frame, and report. |
| int | wfm_frame_crc_ok (const wfm_frame_t * f, const uint8_t * rx_bits) Check a received frame's CRC in place. |
| int | wfm_frame_desc_crc_ok (const wfm_frame_desc_t * d, const uint8_t * rx_bits) Check a received frame's CRC against any description that has one. |
| int | wfm_frame_desc_layout (const wfm_frame_desc_t * d, wfm_frame_desc_layout_t * out) Derive every field offset, every stage span and both lengths. |
| int | wfm_frame_describe (const wfm_frame_t * f, wfm_frame_desc_t * out) Express a wfm_frame_t as awfm_frame_desc_t . |
| int | wfm_frame_field_index (const wfm_frame_desc_t * d, const char * name) Index of the field called name , or -1. |
| int | wfm_frame_layout (const wfm_frame_t * f, wfm_frame_layout_t * out) Fill out with the field offsets. |
| size_t | wfm_frame_nbits (const wfm_frame_t * f) Total frame bits, or 0 if the geometry is empty. |
| size_t | wfm_seq_bits (const wfm_seq_t * s, uint8_t * out, size_t max_out) Write s's bits, whatever produces them. Returns the count. |
Macros¶
| Type | Name |
|---|---|
| define | WFM_FRAME_CRC_BITS 16uBits of CRC-16-CCITT, when a frame carries one. |
| define | WFM_FRAME_MAX_FIELDS 16Fields one description may carry. |
| define | WFM_FRAME_MAX_STAGES 8Stages one description may carry. |
| define | WFM_FRAME_NAME_MAX 16Bytes a field's name may use, NUL included. |
Detailed Description¶
One struct saying what a frame contains, used by the generator that builds it and by the measurer that scores it. The DSSS assembler already stated the reason it must be shared — it is "assembled in one place so TX and RX can
never drift" — and this generalises that from one waveform to all of them: wfm_frame_dsss_chips() now builds these bits and spreads them, rather than carrying a second copy of the layout.
It describes BITS¶
Not chips, not samples, not levels. Spreading, pulse shaping, oversampling, carrier and SNR layer above and stay wfm_synth's job. That boundary is what lets one descriptor serve an unspread BPSK stream and a two-code DSSS burst alike.
Every field is a sequence, and the generators already exist¶
The preamble, the sync word and the payload are all wfm_seq_t, so "a Gold
sync" is a configuration rather than a feature, and pn_create() / gold_create() stay the only implementations of those sequences.
The generated kinds are the ones that matter. A literal array is what a caller with real data has; a PN or Gold descriptor is a handful of numbers a receiver can REGENERATE, which is what makes a long-record BER practical — truth for a million-symbol run without a million-symbol array, and a capture reproducible from its metadata alone.
The CRC is the one we already have¶
dp_crc16_ccitt(), over the payload only, MSB-first, carried as the same int crc flag wfm_frame_dsss_chips() already took. A second CRC would be a wire-format decision and nothing is asking for one.
See also: docs/design/rx-test.md section 7
Public Types Documentation¶
enum wfm__frame_8h_1a385c44f6fb256e5716a2302a5b940388¶
Field indices wfm_frame_describe writes, in wire order.
enum wfm__frame_8h_1a385c44f6fb256e5716a2302a5b940388 {
WFM_FRAME_FIELD_PREAMBLE = 0,
WFM_FRAME_FIELD_SYNC = 1,
WFM_FRAME_FIELD_PAYLOAD = 2,
WFM_FRAME_FIELD_CRC = 3
};
enum wfm_seq_kind_t¶
Where a run of bits comes from.
enum wfm_stage_kind_t¶
Stage kinds doppler itself names.
enum wfm_stage_kind_t {
WFM_STAGE_CRC16 = 0,
WFM_STAGE_RS = 1,
WFM_STAGE_RANDOMISE = 2,
WFM_STAGE_CONV = 3,
WFM_STAGE_INTERLEAVE = 4,
WFM_STAGE_USER = 0x1000u
};
A stage's kind is an open uint32_t, not this enumeration. These are the values doppler has allocated; a caller allocates its own from WFM_STAGE_USER upward and supplies the kernel through wfm_frame_ops_t. That is the difference between a description a caller can extend and a fixed menu — and a closed enum here would make "a mission that is not CCSDS" a pull request against this header rather than a configuration, which is the opposite of the point.
The value is only ever a lookup key. Nothing in this component switches on it exhaustively, so an unrecognised kind is not undefined behaviour: it finds no kernel and the assembly is REFUSED, which is the honest answer and is the same one a declared-but-unsupplied stage already gets.
Public Functions Documentation¶
function wfm_dsss_desc_chips¶
Build a two-code DSSS burst from a description: assemble, spread.
size_t wfm_dsss_desc_chips (
const wfm_frame_desc_t * d,
const wfm_frame_ops_t * ops,
const uint8_t * acq_code,
size_t acq_len,
size_t acq_reps,
const uint8_t * data_code,
size_t data_len,
uint8_t * out,
size_t max_out
)
The general form of wfm_frame_dsss_chips, and the only spreader — the four-field entry point is this one with the description filled in.
**The preamble is not a field of d, by design.** It is unmodulated, unspread and uncoded, because it is the coherent pull-in target a receiver correlates raw chips against; a stage covering "the whole
frame" therefore covers everything that is spread and not the preamble. That is the one place a DSSS burst's description differs from any other source's, and it is why this function takes the preamble separately.
A stage whose kernel ops does not supply makes the assembly fail and the burst is REFUSED — never transmitted with the stage quietly missing, which would produce a waveform that decodes against itself and syncs to nothing.
Parameters:
ddescription of the spread frame.opskernels beyond the built-in CRC; may be NULL.acq_codepreamble chips (0/1); NULL when there is no preamble.acq_lenpreamble length in chips.acq_repspreamble repetitions.data_codespreading code (0/1), lengthdata_len.data_lenchips per frame bit.outreceives the burst, one chip per byte.max_outcapacity ofout; must be at least wfm_dsss_desc_nchips.
Returns:
chips written, or 0 if the geometry is refused, a stage has no kernel, or max_out is too small.
function wfm_dsss_desc_nchips¶
Chip count of a DSSS burst built from a description.
size_t wfm_dsss_desc_nchips (
const wfm_frame_desc_t * d,
size_t acq_len,
size_t acq_reps,
size_t data_len
)
acq_len * acq_reps + out_bits * data_len, where out_bits is what leaves the description's last emitting stage — so an inner code that doubles the frame doubles the burst, and nothing here restates the arithmetic the layout already did.
Parameters:
dthe description of everything that gets SPREAD.acq_lenpreamble code length in chips (0 = no preamble).acq_repspreamble repetitions.data_lenspreading-code length, i.e. chips per frame bit.
Returns:
burst chips, or 0 if the description is refused, or it has bits and data_len is 0, or there is nothing to transmit.
function wfm_frame_add_derived¶
Append a named DERIVED field — one a stage will fill. Returns its index, or -1.
A field with a declared length and no source: a CRC trailer, a block of R-S check symbols. Its producer is wired by wfm_frame_add_stage, not named here, because a stage does not exist yet when the field it derives is appended — fields are ordered by POSITION and stages by APPLICATION, and this is where those two orders meet.
Parameters:
dthe description.namethe field's name, or NULL/"" for anonymous.bitsits length, which its stage decides and the caller states.
Returns:
the new field's index, or -1 on NULL, a full description, a zero bits, or a name already taken.
function wfm_frame_add_field¶
Append a named field. Returns its index, or -1.
int wfm_frame_add_field (
wfm_frame_desc_t * d,
const char * name,
const wfm_seq_t * seq,
size_t reps
)
The building half of the description, and the reason a name is worth carrying: a caller says what a field IS rather than counting positions, and the stage that covers it says so by name too.
Parameters:
dthe description; appended in wire order.namethe field's name, or NULL/"" to leave it anonymous.seqwhere the bits come from; copied by value, so the LITERAL kind still borrows the caller's array and the caller still owns it for as long asdis used.repsrepetitions ofseq, verbatim; 0 means one.
Returns:
the new field's index, or -1 if d or seq is NULL, the description is full, or name is already taken.
function wfm_frame_add_stage¶
Append a stage covering [first .. last] BY NAME. Returns its index, or -1.
int wfm_frame_add_stage (
wfm_frame_desc_t * d,
uint32_t kind,
const char * first,
const char * last
)
The cover is the whole point of the representation and this is the form that reads: add_stage(d, WFM_STAGE_CRC16, "payload", "crc") says what three integers used to.
It wires a derived field's producer for you, and that is applying an invariant rather than adding one: wfm_frame_desc_layout already refuses a description whose derived field is not the LAST of its producing stage's cover, so a field with a declared length and no source sitting at the end of this cover has exactly one possible producer. It is wired here so a caller cannot state it a second, different way.
Parameters:
dthe description.kinda wfm_stage_kind_t value, or a caller's own from WFM_STAGE_USER up.firstname of the first field covered.lastname of the last field covered; may equalfirst.
Returns:
the new stage's index, or -1 on NULL, a full description, a name neither field carries, or last before first.
function wfm_frame_assemble¶
Materialise a description: run every field, then every stage.
size_t wfm_frame_assemble (
const wfm_frame_desc_t * d,
const wfm_frame_ops_t * ops,
uint8_t * out,
size_t max_out
)
The general form of wfm_frame_bits. Fields are written in wire order, then each stage is applied over the span wfm_frame_desc_layout gave it — over that span and no other, which is the whole content of the coverage table a standard's framing turns out to be.
Parameters:
dthe description.opskernels for the stage kinds beyond the built-in CRC; may beNULLwhen there are none.outreceives the unpacked output, one bit per byte.max_outcapacity ofoutin bits; must be at least the layout'sout_bits.
Returns:
The bits written, or 0 if the description is refused, a stage has no kernel, a field cannot be built, or max_out is too small — in which case out is untouched.
function wfm_frame_bits¶
Materialise the frame as one flat 0/1 bit array.
Generated fields are produced here, from the descriptor, so a receiver holding the same handful of numbers regenerates the identical bits.
Parameters:
fthe frame.outoutput, one bit per byte.max_outcapacity ofout.
Returns:
bits written, or 0 if the geometry is empty, a field is unbuildable (a LITERAL with no array, a PN with no register width), or max_out is too small.
function wfm_frame_check¶
Undo a description's stages over a received frame, and report.
int wfm_frame_check (
const wfm_frame_desc_t * d,
const wfm_frame_ops_t * ops,
uint8_t * bits,
wfm_frame_rx_t * rx
)
The receive mirror of wfm_frame_assemble, reading the same description — so the two cannot disagree about which stage covered what, which is the failure the whole representation exists to prevent. Stages are reversed in the OPPOSITE order to the one they were applied in, each over the span the layout gives it.
This is what makes a truth-free frame error rate possible on a coded link, and it is a strictly better detector than a CRC. A CRC says one bit: right or wrong. An outer code says how much repair it took — ok == units with a rising symbols is margin being spent, visible before it is lost. A caller wanting only good frames compares ok with units; one doing accounting reads the rest.
It begins AFTER the inner code and after frame synchronisation, for the reason ccsds_tm_frame.h gives at length: a Viterbi is streaming and emits its decisions depth bits late, so the bits of one frame are not a function of that frame's symbols alone, and the marker that says where a frame starts is only readable once the inner code is undone. A stage with no undo kernel is reported as not checked, never as passed.
Parameters:
dthe description the bits are laid out by.opskernels for the stage kinds beyond the built-in CRC; may beNULL.bitsthe layout'sframe_bitsreceived bits, one per byte, CORRECTED IN PLACE by any stage that repairs.rxreceives the per-stage outcome; may beNULL.
Returns:
1 when every stage that was checked came out good, 0 when one did not, or -1 if the description is refused. A description with no checking stage at all returns -1, not 1: "carries no check" and "the check passed" are different answers, and an FER that conflated them would score every unprotected frame as perfect.
function wfm_frame_crc_ok¶
Check a received frame's CRC in place.
This is what makes a truth-free frame error rate possible. It needs the layout and the received bits and no payload truth at all — so it works on a real capture, and unlike a self-referenced EVM or a blind M2M4 it still catches a false lock, because a rotated constellation fails the check rather than looking clean.
Parameters:
fthe frame the bits are laid out by.rx_bitsreceived bits,wfm_frame_nbits(f)of them.
Returns:
1 pass, 0 fail, -1 if the frame carries no CRC (or on NULL).
function wfm_frame_desc_crc_ok¶
Check a received frame's CRC against any description that has one.
The general form of wfm_frame_crc_ok, and the same truth-free claim: it needs the description and the received bits and no payload truth at all. What the CRC protects is everything its stage covers except the trailer that stage derived — read back from the same rule the assembler writes by, so the two cannot disagree about where the trailer is.
Parameters:
dthe description the bits are laid out by.rx_bitsreceived bits, the layout'sframe_bitsof them.
Returns:
1 pass, 0 fail, -1 if the description carries no CRC stage (or on NULL). The three are distinct on purpose: an FER that read "carries no check" as "the check failed" would count every unprotected frame as an error.
function wfm_frame_desc_layout¶
Derive every field offset, every stage span and both lengths.
The one operation both shipped framers already have, widened: this is wfm_frame_layout()'s arithmetic and ccsds_tm_frame_layout()'s, with the field and stage lists supplied rather than fixed.
A derived field whose producing stage covers no caller-supplied bits is dropped to zero length — which is the general form of the rule wfm_frame_layout has always applied, that a CRC over an empty payload protects nothing and is not emitted.
An EMITTING stage (emit_num set) is refused unless it covers the whole frame, and a second one is refused outright. Refusing here is the point: such a description used to lay out perfectly and then be unassemblable for ever, because out_bits was computed from the cover while wfm_frame_assemble hands the kernel the whole frame. The caller got a 0 from assemble and no way to learn that the geometry, not the data, was wrong. Geometry is decided here, so it is refused here.
A field that declares bits but supplies no sequence is DERIVED, and one that names no producing stage (derived_by zero) is refused for the same reason. It used to lay out at zero length: the frame came out short, the stage that should have filled the field ran over a cover whose tail no longer existed, and the caller got a record rather than an error. Every reader funnels through here, so refusing at this one point covers the scene JSON and the CLI as well as the builder — which cannot reach the state at all, since wfm_frame_add_stage wires the producer from the cover it is given.
Parameters:
dthe description.outreceives the layout.
Returns:
0, or -1 if d or out is NULL, a count or a cover runs past its array, a derived field names no producing stage, or an emitting stage covers less than the whole frame or is not the only one.
function wfm_frame_describe¶
Express a wfm_frame_t as awfm_frame_desc_t .
The bridge that makes the closed struct a configuration rather than a rival: four fields in wire order, plus one CRC stage covering the payload and the trailer it derives. Exported because it is also the worked example — the shortest complete answer to "what does a description of my frame look like".
Parameters:
fthe frame.outreceives the description.
Returns:
0, or -1 if either argument is NULL.
function wfm_frame_field_index¶
Index of the field called name , or -1.
The lookup the whole naming idea rests on, and it is deliberately the ONLY one: names resolve to indices here and nowhere else, so every existing index-taking entry point keeps working unchanged and there is one place a rename can be wrong.
An empty or NULL name finds nothing rather than matching the first unnamed field — an unnamed field is anonymous, not named "", and matching it would make an unnamed description answer questions about fields it does not have.
Parameters:
dthe description.namethe field name, NUL-terminated.
Returns:
the field's index, or -1 if d or name is NULL, name is empty, or no field carries it.
function wfm_frame_layout¶
Fill out with the field offsets.
The arithmetic both directions need, computed once. Today it is inline in wfm_frame_dsss_nchips(), and a receiver scoring a frame would have to recompute it — which is exactly how TX and RX drift apart.
Returns:
0, or -1 if f or out is NULL.
function wfm_frame_nbits¶
Total frame bits, or 0 if the geometry is empty.
Parameters:
fthe frame; must be non-NULL.
function wfm_seq_bits¶
Write s's bits, whatever produces them. Returns the count.
The one place a wfm_seq_t becomes bits. A descriptor materialises its own fields through this, and a consumer that takes a RAW ARRAY rather than a description the DSSS chip builder is the one in this tree calls it to expand a generated sequence into a buffer first. Without that, bits is NULL for every generated kind and the array consumer reads through it.
Parameters:
sthe sequence; a LITERAL copies, the generated kinds run their generator.outreceivess->lenbits, one per byte.max_outcapacity; 0 is returned ifs->lenexceeds it.
Returns:
bits written, or 0 if the sequence is unbuildable (a LITERAL with no array, a length past max_out, a generator that refused its own parameters).
Macro Definition Documentation¶
define WFM_FRAME_CRC_BITS¶
Bits of CRC-16-CCITT, when a frame carries one.
define WFM_FRAME_MAX_FIELDS¶
Fields one description may carry.
Raised from 8 against a measurement rather than a feeling: the deepest description doppler builds today is SIX fields (ASM, preamble, sync, payload, CRC, R-S parity) and FIVE stages, so 8 left room for two more fields — and a user frame that adds a header and a tail to that shape reaches the old ceiling exactly. The descriptor is a POD carried by value, so the cost is bytes on a stack frame: 1136 -> 2152, which is still a comfortable local.
define WFM_FRAME_MAX_STAGES¶
Stages one description may carry.
define WFM_FRAME_NAME_MAX¶
Bytes a field's name may use, NUL included.
The documentation for this class was generated from the following file native/inc/wfm/wfm_frame.h