Skip to content

macaris64/cfs-msgid-sentinel

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

cFS MsgID Sentinel (cfs-msgid-sentinel)

Prevent silent runtime failures caused by cFS MsgID collisions.

cfs-msgid-sentinel scans NASA cFS header files, extracts DEFAULT_*_TOPICID definitions, classifies each topic into one of the four collision domains (channels), computes MsgIDs, and reports collisions/near-misses as:

  • CLI output (table / JSON / Markdown summary)
  • GitHub Actions annotations (::error / ::warning)
  • GitHub Job Summary ($GITHUB_STEP_SUMMARY)
  • GitHub Action outputs ($GITHUB_OUTPUT)

Quick start (30 seconds)

Run locally (from repo)

python -m pip install .
cfs-msgid-sentinel --scan-path .

Run locally (editable dev install)

python -m pip install -e ".[dev]"
cfs-msgid-sentinel --scan-path .

Run in CI (GitHub Actions)

Add .github/workflows/msgid-check.yml:

name: MsgID collision check
on: [push, pull_request]

jobs:
  msgid-sentinel:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: ./
        with:
          scan-paths: "."
          topicid-pattern: "**/*_topicids.h"
          msgid-pattern: "**/*_msgids.h"
          fail-on-collision: "true"
          near-miss-gap: "2"
          report-format: both

cFS collision lab (local, repo-internal)

This repo includes a local-only collision lab that clones upstream NASA cFS into ./cFS/ (gitignored), then generates 10 apps under cFS/apps/ with intentionally duplicated TopicIDs so cfs-msgid-sentinel can be validated against a real-looking tree.

1) Clone cFS into this repo (gitignored)

git clone --depth 1 https://github.com/nasa/cFS.git cFS

Also ensure cFS/ remains ignored (see .gitignore).

2) Generate 10 collision-heavy apps + per-app MESSAGES.md

python scripts/generate_collision_lab_apps.py --cfs-root cFS --count 10 --prefix msgid_collision

Each generated app contains:

  • fsw/inc/<app>_topicids.h
  • MESSAGES.md (expected TopicID/MsgID values for that app)

3) Expected collisions

Collision design:

  • Apps: msgid_collision_01msgid_collision_10
  • Bases (defaults):
    • PLATFORM_CMD: 0x1800
    • PLATFORM_TLM: 0x0800
  • Intentional duplicates: TopicIDs 0x00010x0004 are reused across all 10 apps for both CMD-like and TLM-like names.

Expected collision groups (8 total):

Channel TopicID MsgID
PLATFORM_CMD 0x0001 0x1801
PLATFORM_CMD 0x0002 0x1802
PLATFORM_CMD 0x0003 0x1803
PLATFORM_CMD 0x0004 0x1804
PLATFORM_TLM 0x0001 0x0801
PLATFORM_TLM 0x0002 0x0802
PLATFORM_TLM 0x0003 0x0803
PLATFORM_TLM 0x0004 0x0804

The machine-readable expected-collisions file is expected_collisions.json, which is generated by scripts/generate_collision_lab_apps.py and gitignored (local artifact).

Usage

CLI examples

# Basic scan
cfs-msgid-sentinel --scan-path .

# If your headers live under specific subtrees
cfs-msgid-sentinel --scan-path apps --topicid-pattern "**/fsw/inc/*_topicids.h"

# Add near-miss warnings (IDs within N of each other, per channel)
cfs-msgid-sentinel --scan-path . --near-miss-gap 3

# Machine-readable output
cfs-msgid-sentinel --scan-path . --format json

# CI-friendly (no ANSI), but still return success even if collisions exist
cfs-msgid-sentinel --scan-path . --no-color --no-fail-on-collision

# Emit an audit report (also writes collusion-report.txt)
cfs-msgid-sentinel --scan-path . --report

GitHub Actions examples

Typical cFS mission repo

- uses: ./
  with:
    scan-paths: "."
    topicid-pattern: "**/*_topicids.h"
    msgid-pattern: "**/*_msgids.h"
    fail-on-collision: "true"
    near-miss-gap: "0"
    report-format: both

Consume JSON output (allocation-map)

- uses: ./
  id: sentinel
  with:
    report-format: json
    fail-on-collision: "false"

- name: Print summary
  run: |
    echo '${{ steps.sentinel.outputs.allocation-map }}' | jq '.summary'

Configuration: how it finds headers (and what it expects)

1) What gets scanned

  • Root(s):
    • CLI: --scan-path <path> (comma-separated roots)
    • Action: scan-paths (comma-separated roots)
  • Topic ID headers:
    • CLI: --topicid-pattern (default **/*_topicids.h)
    • Action: topicid-pattern (default **/*_topicids.h)
    • The parser looks for:
#define DEFAULT_<NAME>_TOPICID <hex_or_decimal>
  • MsgID headers (Tier-1 channel classification):
    • CLI: --msgid-pattern (default **/*_msgids.h)
    • Action: msgid-pattern (default **/*_msgids.h)
    • From msgid-pattern, the scanner also derives a sibling pattern for *_msgid_values.h.

2) How MsgIDs are computed

cfs-msgid-sentinel computes:

Final MsgID = Base | TopicID

Topic IDs are collision-checked within each of the four channels:

  • PLATFORM_CMD
  • PLATFORM_TLM
  • GLOBAL_CMD
  • GLOBAL_TLM

3) Where base addresses come from

Base addresses come from (highest priority first):

  1. Explicit overrides (CLI flags / Action inputs): cmd-base, tlm-base, global-cmd-base, global-tlm-base
  2. Auto-detected mapping header: default_cfe_core_api_msgid_mapping.h (if present anywhere under your scan roots)
  3. Built-in defaults:
    • PLATFORM_CMD: 0x1800
    • PLATFORM_TLM: 0x0800
    • GLOBAL_CMD: 0x1860
    • GLOBAL_TLM: 0x0860

4) Expected project structure (minimal)

You don’t need a special layout—just ensure the scan root(s) include your headers:

<mission_root>/
  apps/
    <app>/
      fsw/inc/<app>_topicids.h
  ...
  cfe/
    ... *_msgids.h
    ... *_msgid_values.h
  ...

GitHub Action inputs / outputs

Inputs

Input Description Default
scan-paths Root directories to scan (comma-separated) .
topicid-pattern Glob pattern(s) for topic ID headers (comma-separated) **/*_topicids.h
msgid-pattern Glob pattern(s) for MsgID headers (comma-separated) **/*_msgids.h
cmd-base Platform command MsgID base address 0x1800
tlm-base Platform telemetry MsgID base address 0x0800
global-cmd-base Global command MsgID base address 0x1860
global-tlm-base Global telemetry MsgID base address 0x0860
fail-on-collision Fail the workflow if a collision is detected true
near-miss-gap Warn about topic IDs within N of each other (0 to disable) 0
report-format Output format: summary, json, or both both

Outputs

Output Description
collision-count Number of collisions found
has-collisions true if any collisions were found
allocation-map JSON string containing summary + full allocation map

Troubleshooting

  • No topic ID files found

    • Check you’re scanning the right root (--scan-path / scan-paths).
    • Tighten or loosen --topicid-pattern / topicid-pattern.
  • Lots of heuristic classification

    • Ensure your *_msgids.h headers are included by --msgid-pattern / msgid-pattern.
    • If your repo uses different filenames, set a custom msgid-pattern.
  • Different base addresses than the defaults

    • Prefer committing a correct default_cfe_core_api_msgid_mapping.h under the scan roots.
    • Or override bases via CLI flags / Action inputs.

Development

Local development

Create a virtualenv, install dev deps, and run the safety-critical quality gate:

python -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"

python -m pip check
ruff format --check .
ruff check .
pylint src/cfs_msgid_sentinel
mypy src
bandit -q -r src
lizard -l python -C 10 src
pytest

Manual smoke run (fixtures)

cfs-msgid-sentinel --scan-path tests/fixtures --topicid-pattern "**/real/*_topicids.h"

Required real-workspace validation

CI runners cannot access your local cFS tree, so this is a required local gate:

python scripts/validate_real_workspace.py --scan-path /home/aero/Desktop/cFS/apps

Build / distribution smoke

python -m pip install build
python -m build
python -m pip install dist/*.whl
cfs-msgid-sentinel --help

License

Apache-2.0. See LICENSE.

About

Detect Message ID collisions across NASA cFS missions. GitHub Action + standalone CLI.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors