F439_CPP_TX-RX_LoRa_Project
Loading...
Searching...
No Matches
Protocol Verification and QA Harness

Overview

The RadioLink protocol-verification harness exercises the hardened Wire v3 RX path with hostile and boundary-condition inputs.

The intent of this harness is to verify that RX continues operating safely when presented with malformed, malicious, replayed, or boundary-value frames, and to report a deterministic pass or fail result for every case.

The harness is a structured test suite. It transmits a fixed set of QA frames in a stable order, evaluates each received frame against an expected outcome, prints a per-test verdict, and emits a final tallied report.

Verification Model

The verification model intentionally separates roles:

  • TX runs a deterministic QA frame generator from qaTests/qaApp/qa_app.c
  • RX runs the normal radio receive/IRQ path and calls the production RadioLink_ParseWireV3Frame() parser

This approach validates the real RX rejection path rather than a test-only RX parser. The RX side compares the parser's accept-or-reject outcome against the expected outcome recorded for each case and derives a PASS or FAIL verdict. Expected QA rejections are logged and must not invoke Error_Handler().

Enabling the QA Harness

Protocol verification is enabled by defining:

#define RADIOLINK_QA_TEST

When enabled, Core/Src/main.c routes DIO1 EXTI events to QaApp_OnDio1Exti() and runs QaApp_Loop() instead of RadioApp_Loop(). The harness selects TX or RX behavior using sx1262Role. Both boards are flashed with the same QA-mode binary.

Under RADIOLINK_QA_TEST the node identifier used for crypto key derivation is fixed, so the TX and RX boards derive identical keys regardless of their hardware UID. This is required because the QA frame builders construct frames with a fixed node identifier.

Test Correlation

Each QA frame is correlated to its test case by the msgCounter header field rather than by arrival order. TX encodes the case index into msgCounter (QA_RUN_BASE + caseIndex), and RX recovers the index from the cleartext header. Because the header is not encrypted, correlation works for both accepted and rejected frames, and a lost frame does not desync the suite.

The RX side also applies a per-test timeout. If a frame for a given case does not arrive within its deadline, that case is recorded as a FAIL with a "no frame received" reason and the suite advances. This guarantees the suite always completes and prints a final report.

Execution Expectations

During QA execution, TX sends one case per second in a stable order. TX logs the test name, sessionSeqId, msgCounter, frame length, and expected RX result before queuing each frame.

RX must:

  • reject invalid or hostile frames without halting
  • avoid mutating replay state on rejected frames
  • continue processing subsequent traffic
  • accept valid frames, including maximum legal payload cases

For each received frame RX prints a test banner, the expected result, the parser outcome, and a PASS or FAIL verdict. After the final case RX prints a report giving the total, passed, and failed counts, and lists any failed tests by name.

Reject-Reason Logging

When RADIOLINK_DEBUG_RX_REJECT_REASON_ENABLE is set, RadioLink_ParseWireV3Frame() logs which validation gate rejected a frame:

  • gate=header structural or header-field validation failed
  • gate=cmac CMAC authentication failed
  • gate=replay replay freshness check failed
  • gate=decrypt payload decryption failed

This makes the verdict meaningful at the gate level: a reject-expecting case is only fully trustworthy when it is rejected at the gate its corruption targets. The QA suite is expected to show truncated-frame, length-mismatch, oversized-payload, and invalid-nodeId rejected at gate=header, cmac-failure at gate=cmac, and replay-duplicate at gate=replay.

Expected TX Log Pattern

TX emits lines similar to:

QA TX: start test=truncated-frame sess=1 ctr=1000 expected=rejected: below minimum Wire v3 frame length
QA TX: queued test=truncated-frame len=10 sess=1 ctr=1000 expected=rejected: below minimum Wire v3 frame length
QA TX: done test=truncated-frame sess=1 ctr=1000 expected=rejected: below minimum Wire v3 frame length

Expected RX Log Pattern

RX emits a suite header, then per-test banners and verdicts, then a final report similar to:

[1/8] truncated-frame
      expected : rejected: below minimum Wire v3 frame length
      verdict  : PASS

QA SUITE COMPLETE
Total tests : 8
Passed      : 8
Failed      : 0
Result      : ALL PASS

Verified Test Coverage

The TX sequencer actively transmits the following cases in order:

  1. Truncated Frame Rejection

    A frame shorter than the minimum Wire v3 frame length is injected.

    Expected RX behavior:

    • frame rejected at the header gate
    • no system halt
    • receiver continues running
  2. Frame Length Mismatch

    A frame where the declared payload length does not match the received frame size is injected.

    Expected RX behavior:

    • frame rejected at the header gate
    • no replay-state corruption
    • receiver continues running
  3. CMAC Authentication Failure

    A validly structured frame with an intentionally corrupted authentication tag is injected.

    Expected RX behavior:

    • frame rejected at the CMAC gate
    • no plaintext accepted
    • receiver continues running
  4. Replay Rejection

    The sequencer sends a valid replay seed frame and then sends an exact duplicate with the same nodeId, sessionSeqId, and msgCounter values.

    Expected RX behavior:

    • first frame accepted
    • duplicate rejected at the replay gate
    • replay state remains correct
  5. Maximum Payload Boundary Acceptance

    A frame containing RADIOLINK_WIRE_V3_MAX_PLAINTEXT_LEN plaintext bytes is transmitted.

    Expected RX behavior:

    • frame accepted
    • payload decrypted successfully
  6. Oversized Payload Rejection

    A frame declaring a plaintext length larger than RADIOLINK_WIRE_V3_MAX_PLAINTEXT_LEN is injected.

    Expected RX behavior:

    • frame rejected at the header gate
    • receiver continues running
  7. Invalid NodeId Rejection

    A frame whose nodeId byte is overwritten after frame construction is injected. The QA builder sets nodeId to 0xFF. Because the nodeId is covered by the CMAC, modifying it after the tag is computed invalidates the authentication tag.

    Expected RX behavior:

    • frame rejected (tampered header)
    • replay state not indexed by the injected nodeId
    • receiver continues running

RX Rejection Behavior Requirement

Rejected protocol frames must be discarded without invoking Error_Handler().

The verified RX contract is:

  • discard the rejected frame
  • preserve receiver liveness
  • preserve replay-state integrity
  • continue processing future frames

Known Limitations

The harness has one known limitation in this revision:

  • Sub-header-length correlation. A frame too short to contain the msgCounter header field cannot be correlated by msgCounter and is attributed to the truncated-frame case. This is correct for the present suite because the truncated frame is the only deliberately sub-header-length case. If further sub-header-length cases are added, the correlation scheme must be revised.

Future Logging Hooks

This verification work also establishes the categories that should eventually map to unique structured logging identifiers for remote syslog forwarding, for example:

  • RX_REJECT_TRUNCATED
  • RX_REJECT_LENGTH_MISMATCH
  • RX_REJECT_PAYLOAD_TOO_LARGE
  • RX_REJECT_CMAC_FAIL
  • RX_REJECT_REPLAY
  • RX_REJECT_TAMPERED_HEADER