Skip to content

Update Label B6 — full FANS 1/A ADS-C decode#422

Open
thepacket wants to merge 1 commit intoairframesio:masterfrom
thepacket:plugin/label-b6-update
Open

Update Label B6 — full FANS 1/A ADS-C decode#422
thepacket wants to merge 1 commit intoairframesio:masterfrom
thepacket:plugin/label-b6-update

Conversation

@thepacket
Copy link
Copy Markdown

Replaces the stub Label_B6_Forwardslash plugin with a full FANS 1/A ADS-C Provide-ADS-Report decoder.

Handles three envelope forms:

  1. 14ASQ0431/SKYSWSQ.ADS.9V-SMI07… — standard with flight prefix
  2. J70AQR040X/MELCAYA.ADS.A7-ANT030BA821 — 2-char msg block
  3. /RPHIAYA.ADS.HL8501070ED832A9374985A7941D0E… — direct (no flight prefix)

Tail/payload split uses a registration-pattern cascade (Korean HL, Taiwan/China B-, European dashed, US N-numbers, Japanese JA) to correctly split all-hex tails.

Surfaces ground, tail, first-byte contract request number. Binary payload preserved as hex for downstream ARINC-745 decoding.

npm run build passes.

Extended to handle additional sub-formats and/or bug fixes.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@thepacket has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 55 minutes and 7 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 55 minutes and 7 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8cb9d5c2-51cd-4d0f-ae9e-0be966d2596d

📥 Commits

Reviewing files that changed from the base of the PR and between 10f9f69 and e777f5a.

📒 Files selected for processing (1)
  • lib/plugins/Label_B6.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Replaces the previous stub Label B6 decoder with a structured FANS 1/A ADS-C “Provide ADS Report” envelope parser, extracting addressing metadata and preserving the ADS-C payload for downstream decoding.

Changes:

  • Implemented ADS-C .ADS. envelope parsing for multiple observed message forms (prefixed and direct).
  • Added tail/payload splitting logic using a registration-pattern cascade plus fallback heuristics.
  • Surfaced key extracted fields (flight, ground, tail, contract request number, payload hex) into raw and formatted items.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/plugins/Label_B6.ts
Comment on lines 49 to +53
decode(message: Message, options: Options = {}): DecodeResult {
const decodeResult = this.defaultResult();
decodeResult.decoder.name = this.name;
decodeResult.formatted.description = 'CPDLC Message';
decodeResult.message = message;
const decodeResult = this.initResult(
message,
'FANS-1/A ADS-C Provide ADS Report',
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer reviewer agree — this is a Must Fix. 200+ lines of new envelope/regex/cascade logic without a single unit test is a regression-risk magnet. Please add Label_B6.test.ts covering at least: the three documented envelope forms; an HL-tail-with-all-hex case (the motivation for the cascade); an invalid envelope (decoded:false); and a short-payload case (verifies the ADSNOTE row). Without this, future changes to the regex or cascade will silently break decoding.

Comment thread lib/plugins/Label_B6.ts
return {
labels: ['B6'],
preambles: ['/'],
preambles: ['/', 'J', '1'],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer reviewer agree — Must Fix. The dispatcher routes by labels AND preambles, so any B6 envelope starting with a sublabel character not in ['/', 'J', '1'] is silently dropped. Recommend dropping preambles entirely and letting labels: ['B6'] plus the .ADS. envelope match drive selection. (If preambles must stay for performance reasons, broaden to all valid 2-char sublabel starts — though that approaches [A-Z0-9] which is effectively no filter.)

Comment thread lib/plugins/Label_B6.ts
Comment on lines +78 to +82
const m =
text.match(envelopeRe(2)) ||
text.match(envelopeRe(1)) ||
text.match(envelopeRe(0)) ||
text.match(directRe);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer reviewer agree — Should Fix. Pick one: either extend the matcher to try envelopeRe(3) first, or tighten the docblock to '0–2 chars' to match what's actually implemented. The current discrepancy will mislead future maintainers. If 3-char msgblocks are observed in the wild (the docblock implies they are), extending the matcher is the right call — the cascade ordering (try longest first) is already the correct strategy.

Comment thread lib/plugins/Label_B6.ts
const afterCand = rest.substring(cand.length);
if (/^[0-9A-F]*$/i.test(afterCand)) {
tail = cand;
payload = afterCand.toUpperCase();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer reviewer agree — Should Fix. The cascade path uppercases via afterCand.toUpperCase(); the fallback path (payload = rest.substring(...)) preserves casing. Recommend normalizing to uppercase in both paths and updating the docblock to say 'payload normalized to uppercase hex' rather than 'preserved verbatim'. Uppercase is conventional for ARINC hex and matches what most downstream tools (libacars, dumpvdl2) emit.

Comment thread lib/plugins/Label_B6.ts
Comment on lines +119 to +123
if (!tail) {
// Fallback: non-hex scan in the first 8 chars
const window = rest.substring(0, Math.min(8, rest.length));
let lastNonHex = -1;
for (let i = 0; i < window.length; i++) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer reviewer agree — Should Fix. The fallback path can produce non-hex payloads and still mark the message decoded: true, level: full. At minimum: after the fallback split, validate /^[0-9A-F]*$/i.test(payload) and call failUnknown (or setDecodeLevel(decodeResult, false)) if it fails. Better: also reject if payload.length is odd (hex bytes are always pairs). This also has implications for the ADS Contract Request Number extraction at line ~150 which assumes pure hex.

Copy link
Copy Markdown
Contributor

@kevinelliott kevinelliott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Replaces the previous stub Label_B6_Forwardslash plugin (which only emitted 'CPDLC Message' and never set decoded = true) with a full FANS-1/A ADS-C "Provide ADS Report" envelope decoder. Handles three observed envelope forms (standard with flight prefix, 2-char msgblock, direct without sublabel/flight) and uses a registration-pattern cascade (HL, JA, B-, hyphenated, N-numbers) to split tail from binary payload — necessary because hex-only payload bytes can look identical to digit-bearing tails like HL8501.

Verdict

Comment-only review. The previous code was a stub — this is a real, well-documented decoder. Direction is excellent. The tail-splitting cascade is clever and handles the common-case ICAO patterns. Before merge, however, this PR needs unit tests and the qualifiers().preambles mismatch needs to be resolved (see Copilot review below).

Must Fix

  1. No tests. This plugin previously had no test file (Label_B6.test.ts does not exist), and adding 200+ lines of envelope/regex logic without tests is a regression-risk magnet. Please add Label_B6.test.ts covering at least:

    • The three documented envelope forms in the PR body.
    • An HL (Korean) tail with all-hex digits — the case that motivates the cascade.
    • An invalid envelope — verify it returns decoded: false.
    • A short-payload case — verify the ADSNOTE row appears.
    • The preambles filter (see #2 below).
  2. qualifiers().preambles is too narrow. You set ['/', 'J', '1'], but documented envelopes start with 14, J7, and /. The decoder regex accepts any 2-char [A-Z0-9]{2} sublabel, but messages are dispatched to plugins by matching against preambles — so 0x..., 2x..., Ax... envelopes never reach this plugin. Either (a) drop preambles and let the engine route by label only, or (b) broaden to all valid sublabel starts. (Copilot raised this — I agree.)

Should Fix

  • Casing inconsistency: in the cascade path you payload = afterCand.toUpperCase(); in the fallback path you keep original casing. The docblock says payload is "preserved verbatim". Pick one — I'd suggest normalizing to uppercase in both paths and updating the comment to say "payload normalized to uppercase hex".
  • Fallback validates nothing. If the envelope matches but the registration cascade fails, the fallback grabs rest.substring(0, 6) regardless of whether the result is hex. That sets decoded: true, level: full for arbitrary garbage. At minimum, validate payload matches /^[0-9A-F]*$/i after the fallback split, and failUnknown if not. (Copilot raised this — I agree.)
  • 3-char msgblock isn't tried. The docblock says "1–3 chars" but you only try lengths 2, 1, 0. Either tighten the docblock to "0–2 chars" or extend the matcher to try envelopeRe(3) first. Pick whichever matches reality. (Copilot raised this — I agree.)
  • The ADS Contract Request Number is the first byte — you correctly extract payload.substring(0, 2) as hex. Worth noting in the formatted row that this is byte 1 of the ARINC-745 "basic group" header, so consumers know what they're looking at.
  • The payloadIsShort heuristic (≤ 8 hex chars = "control plane") is a reasonable guess, but FANS-1/A actually has well-defined message types — short payloads here are typically Provide ADS Report with a basic-group-only payload, contract requests have their own labels. The annotation as written is helpful for human readers; consider softening from "likely contract-setup" to "may be a short-form ADS report or contract control message — full FANS-1/A ASN.1 decoder needed for definitive type".

Nits

  • The 3-form regex builder envelopeRe(msgLen) is neat but expensive (constructs a new RegExp on every decode). Consider hoisting the three regexes (lengths 0/1/2) to module-level constants.
  • (m.groups as any).sublabel as string | undefined — the named group types are string | undefined already (since TS 4.5 with noUncheckedIndexedAccess they're correctly typed). The as any casts can probably go away.
  • The unshift of the MSGTYP row + later push of SUBLBL, MSGNUM, GND, ADSREQ, ADSPAYLD, ADSNOTE produces a stable order, but it's worth committing to that order in tests so it doesn't drift.

Backward Compat / Regression Risk

  • Previous behavior: stub returned decoded: false, level: 'none', formatted description 'CPDLC Message', no items. Anything matched here was effectively undecoded.
  • New behavior: decoded: true, level: 'full', items list of 4–7 rows.
  • This is a strict upgrade for messages that match the envelope, and a non-change for messages that don't. Caveat: the old description was 'CPDLC Message'. The new description is 'FANS-1/A ADS-C Provide ADS Report'. These are different message classes — the file/class name Label_B6_Forwardslash originally implied CPDLC, and ADS-C is functionally a different protocol. Make sure the project intent for label B6 is ADS-C, not CPDLC. The PR body asserts ADS-C; please confirm with the airframes team before merging.

Test Coverage

Currently zero. See Must Fix #1.

Outstanding Existing Comments

Five inline comments from Copilot. My stance, replied inline:

  1. #3141135599 (no tests): Agree. Must Fix.
  2. #3141135616 (preambles too narrow): Agree. Must Fix.
  3. #3141135624 (3-char msgblock not handled): Agree. Should Fix — at minimum align doc and code.
  4. #3141135634 (payload casing inconsistency): Agree. Should Fix.
  5. #3141135643 (fallback doesn't validate hex): Agree. Should Fix.

All five are actionable and uncontested. Please address before merge.

Thanks @thepacket! This is a substantial improvement over the stub and the docblock is excellent — please add tests, fix the preambles, and address the Copilot comments before merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants