Detect NASA cFS MsgID collisions by scanning topic IDs
Project description
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.hMESSAGES.md(expected TopicID/MsgID values for that app)
3) Expected collisions
Collision design:
- Apps:
msgid_collision_01…msgid_collision_10 - Bases (defaults):
PLATFORM_CMD:0x1800PLATFORM_TLM:0x0800
- Intentional duplicates: TopicIDs
0x0001–0x0004are 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)
- CLI:
- Topic ID headers:
- CLI:
--topicid-pattern(default**/*_topicids.h) - Action:
topicid-pattern(default**/*_topicids.h) - The parser looks for:
- CLI:
#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.
- CLI:
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_CMDPLATFORM_TLMGLOBAL_CMDGLOBAL_TLM
3) Where base addresses come from
Base addresses come from (highest priority first):
- Explicit overrides (CLI flags / Action inputs):
cmd-base,tlm-base,global-cmd-base,global-tlm-base - Auto-detected mapping header:
default_cfe_core_api_msgid_mapping.h(if present anywhere under your scan roots) - Built-in defaults:
PLATFORM_CMD:0x1800PLATFORM_TLM:0x0800GLOBAL_CMD:0x1860GLOBAL_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.
- Check you’re scanning the right root (
-
Lots of heuristic classification
- Ensure your
*_msgids.hheaders are included by--msgid-pattern/msgid-pattern. - If your repo uses different filenames, set a custom
msgid-pattern.
- Ensure your
-
Different base addresses than the defaults
- Prefer committing a correct
default_cfe_core_api_msgid_mapping.hunder the scan roots. - Or override bases via CLI flags / Action inputs.
- Prefer committing a correct
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cfs_msgid_sentinel-1.0.0.tar.gz.
File metadata
- Download URL: cfs_msgid_sentinel-1.0.0.tar.gz
- Upload date:
- Size: 31.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5084c076b45be80e4f0cf01540b128d23b12bb2e7bf70885b7e57446146d4639
|
|
| MD5 |
aef51ac2d01c16d65cce48192c93498c
|
|
| BLAKE2b-256 |
71bee123169d2fd1094b686e467bf066c1df9ad5c5c1de51c1ec6896c0359f2c
|
File details
Details for the file cfs_msgid_sentinel-1.0.0-py3-none-any.whl.
File metadata
- Download URL: cfs_msgid_sentinel-1.0.0-py3-none-any.whl
- Upload date:
- Size: 32.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02f05f7e103dcf5751e990b36111a9d153f237fa885801d2bac983ebd87eb665
|
|
| MD5 |
f45d3b54ff76a5af18f7bef3e6afb891
|
|
| BLAKE2b-256 |
49750b5637b5ef621aeb2e952b37063dcc9d8dcac2738ffcc5019c83169dd3f3
|