Skip to content

fix(result_formatter): preserve full hex for checksums wider than 16 bits#488

Open
fuleinist wants to merge 1 commit into
airframesio:masterfrom
fuleinist:fix/checksum-hex-truncation
Open

fix(result_formatter): preserve full hex for checksums wider than 16 bits#488
fuleinist wants to merge 1 commit into
airframesio:masterfrom
fuleinist:fix/checksum-hex-truncation

Conversation

@fuleinist

@fuleinist fuleinist commented Jul 25, 2026

Copy link
Copy Markdown

Summary

Fixes #484

ResultFormatter.checksum used ('0000' + hex).slice(-4) which silently truncates any checksum wider than 16 bits to its last 4 hex digits. For example:

  • 0xAFF5C → displayed as 0xFF5C (wrong)
  • 0x123456 → displayed as 0x3456 (wrong)
  • 0xDEADBEEF → displayed as 0xBEEF (wrong)

The raw.checksum value is stored correctly; only the formatted.items entry (what downstream consumers render) is corrupted.

Fix

Replaced the truncating pad-and-slice with:

value: '0x' + (decodeResult.raw.checksum >>> 0).toString(16).padStart(4, '0'),

Test coverage

Added lib/utils/result_formatter.test.ts with 6 unit tests:

Test Input Expected output
16-bit round-trip 0x1234 0x1234
Small value padding 0x003b 0x003b
20-bit no truncation 0xaff5c 0xaff5c
24-bit no truncation 0x123456 0x123456
32-bit unsigned CRC 0xdeadbeef 0xdeadbeef
12-bit padding 0xabc 0x0abc

Verification

Test Suites: 98 passed, 98 total
Tests:       460 passed, 9 skipped (pre-existing), 469 total

Full suite green. No regressions.

Summary by CodeRabbit

  • Bug Fixes

    • Improved checksum display to consistently use unsigned hexadecimal values.
    • Preserved full-width values for larger checksums without truncation or sign-related formatting issues.
    • Ensured smaller checksum values are padded to at least four hexadecimal digits.
  • Tests

    • Added coverage for checksum formatting across 12-bit, 16-bit, 20-bit, and 32-bit values.

…bits

ResultFormatter.checksum used ('0000' + hex).slice(-4) which truncates
any checksum > 0xFFFF to its last 4 hex digits. For example, 0xAFF5C
displayed as 0xFF5C and 0xDEADBEEF as 0xBEEF — silently wrong values
in formatted.items that downstream consumers render to users.

Replace with (value >>> 0).toString(16).padStart(4, '0') which:
- Pads values < 0x1000 to 4 hex digits (preserving prior behaviour)
- Preserves the full hex representation for wider checksums
- Coerces to unsigned via >>> 0, avoiding negative-hex display for
  32-bit CRCs whose top bit is set (companion to airframesio#458)

Add result_formatter.test.ts with 6 unit tests covering 12-bit, 16-bit,
20-bit, and 32-bit checksum round-trips.

Fixes airframesio#484
Copilot AI review requested due to automatic review settings July 25, 2026 01:06
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

ResultFormatter.checksum now preserves checksum values wider than 16 bits, formats unsigned 32-bit values correctly, and retains minimum four-digit padding. New Jest tests cover the formatting behavior and numeric storage.

Changes

Checksum formatting

Layer / File(s) Summary
Checksum formatting and validation
lib/utils/result_formatter.ts, lib/utils/result_formatter.test.ts
Checksum values are converted to unsigned hexadecimal strings with at least four digits, while tests cover small, 16-bit, wider, and top-bit-set 32-bit values.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

A rabbit checks the hex at night,
Four little digits padded right.
Wide CRCs now keep their ears,
No vanished bits, no signing fears.
Hop, hop—green tests shine bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: preserving full checksum hex width instead of truncating it.
Linked Issues check ✅ Passed The code and tests address #484 by preserving full hex values, keeping zero-padding for small values, and handling unsigned 32-bit checksums.
Out of Scope Changes check ✅ Passed The changes stay focused on ResultFormatter.checksum and its tests, with no unrelated code paths added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

Copilot AI left a comment

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.

Pull request overview

This PR fixes checksum hex formatting in ResultFormatter.checksum so checksums wider than 16 bits are no longer silently truncated in the formatted output consumed by downstream renderers.

Changes:

  • Replace the prior 4-hex-digit truncation logic with padStart(4, '0') while keeping the full hex for wider checksums.
  • Coerce checksum values to unsigned form for display to avoid negative hex strings for signed-Int32 CRC sources.
  • Add unit tests for ResultFormatter.checksum covering padding and non-truncation scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
lib/utils/result_formatter.ts Updates checksum formatting logic to avoid truncation and improve unsigned display handling.
lib/utils/result_formatter.test.ts Adds unit tests validating checksum formatting across multiple widths and padding cases.

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

code: 'CHECKSUM',
label: 'Message Checksum',
value: '0x' + ('0000' + decodeResult.raw.checksum.toString(16)).slice(-4),
value: '0x' + (decodeResult.raw.checksum >>> 0).toString(16).padStart(4, '0'),
Comment on lines +48 to +56
test('displays large 32-bit CRC without sign artifacts', () => {
// 0xDEADBEEF has the top bit set; toString(16) on a signed int would
// produce a negative string. >>> 0 coerces to unsigned first.
const dr = makeDecodeResult();
ResultFormatter.checksum(dr, 0xdeadbeef);
expect(dr.raw.checksum).toBe(0xdeadbeef);
const item = dr.formatted.items[dr.formatted.items.length - 1];
expect(item.value).toBe('0xdeadbeef');
});

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/utils/result_formatter.test.ts`:
- Around line 40-45: Rename the test case around ResultFormatter.checksum from
“32-bit checksum” to “24-bit checksum,” keeping its assertions and test behavior
unchanged.
- Around line 48-55: Update the test around ResultFormatter.checksum to pass the
signed int32 representation of 0xDEADBEEF rather than the positive unsigned
Number, while continuing to assert the stored checksum and formatted value are
0xdeadbeef.

In `@lib/utils/result_formatter.ts`:
- Line 297: Format the value expression in the result formatter object according
to Prettier, wrapping the concatenation and conversion expression as needed so
the prettier/prettier lint check passes; preserve the existing checksum
formatting behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a469c2fa-a52c-418d-87f9-a8270254dc98

📥 Commits

Reviewing files that changed from the base of the PR and between cdb52c8 and 6ee1d85.

📒 Files selected for processing (2)
  • lib/utils/result_formatter.test.ts
  • lib/utils/result_formatter.ts

Comment on lines +40 to +45
test('preserves full hex for 32-bit checksum (no truncation)', () => {
const dr = makeDecodeResult();
ResultFormatter.checksum(dr, 0x123456);
expect(dr.raw.checksum).toBe(0x123456);
const item = dr.formatted.items[dr.formatted.items.length - 1];
expect(item.value).toBe('0x123456');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename this as a 24-bit checksum test.

0x123456 is a 24-bit value, not a 32-bit value. The suite’s 0xdeadbeef case covers 32-bit formatting, but this test name should accurately describe its input.

Proposed fix
-  test('preserves full hex for 32-bit checksum (no truncation)', () => {
+  test('preserves full hex for 24-bit checksum (no truncation)', () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('preserves full hex for 32-bit checksum (no truncation)', () => {
const dr = makeDecodeResult();
ResultFormatter.checksum(dr, 0x123456);
expect(dr.raw.checksum).toBe(0x123456);
const item = dr.formatted.items[dr.formatted.items.length - 1];
expect(item.value).toBe('0x123456');
test('preserves full hex for 24-bit checksum (no truncation)', () => {
const dr = makeDecodeResult();
ResultFormatter.checksum(dr, 0x123456);
expect(dr.raw.checksum).toBe(0x123456);
const item = dr.formatted.items[dr.formatted.items.length - 1];
expect(item.value).toBe('0x123456');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/utils/result_formatter.test.ts` around lines 40 - 45, Rename the test
case around ResultFormatter.checksum from “32-bit checksum” to “24-bit
checksum,” keeping its assertions and test behavior unchanged.

Comment on lines +48 to +55
test('displays large 32-bit CRC without sign artifacts', () => {
// 0xDEADBEEF has the top bit set; toString(16) on a signed int would
// produce a negative string. >>> 0 coerces to unsigned first.
const dr = makeDecodeResult();
ResultFormatter.checksum(dr, 0xdeadbeef);
expect(dr.raw.checksum).toBe(0xdeadbeef);
const item = dr.formatted.items[dr.formatted.items.length - 1];
expect(item.value).toBe('0xdeadbeef');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exercise the signed int32 input path.

This test passes the positive Number 0xdeadbeef, for which toString(16) already returns deadbeef; it would pass even without >>> 0. Pass the signed representation instead to verify the regression fix.

Proposed fix
     const dr = makeDecodeResult();
-    ResultFormatter.checksum(dr, 0xdeadbeef);
-    expect(dr.raw.checksum).toBe(0xdeadbeef);
+    const signedChecksum = -559038737; // int32 representation of 0xdeadbeef
+    ResultFormatter.checksum(dr, signedChecksum);
+    expect(dr.raw.checksum).toBe(signedChecksum);
     const item = dr.formatted.items[dr.formatted.items.length - 1];
     expect(item.value).toBe('0xdeadbeef');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('displays large 32-bit CRC without sign artifacts', () => {
// 0xDEADBEEF has the top bit set; toString(16) on a signed int would
// produce a negative string. >>> 0 coerces to unsigned first.
const dr = makeDecodeResult();
ResultFormatter.checksum(dr, 0xdeadbeef);
expect(dr.raw.checksum).toBe(0xdeadbeef);
const item = dr.formatted.items[dr.formatted.items.length - 1];
expect(item.value).toBe('0xdeadbeef');
test('displays large 32-bit CRC without sign artifacts', () => {
// 0xDEADBEEF has the top bit set; toString(16) on a signed int would
// produce a negative string. >>> 0 coerces to unsigned first.
const dr = makeDecodeResult();
const signedChecksum = -559038737; // int32 representation of 0xdeadbeef
ResultFormatter.checksum(dr, signedChecksum);
expect(dr.raw.checksum).toBe(signedChecksum);
const item = dr.formatted.items[dr.formatted.items.length - 1];
expect(item.value).toBe('0xdeadbeef');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/utils/result_formatter.test.ts` around lines 48 - 55, Update the test
around ResultFormatter.checksum to pass the signed int32 representation of
0xDEADBEEF rather than the positive unsigned Number, while continuing to assert
the stored checksum and formatted value are 0xdeadbeef.

code: 'CHECKSUM',
label: 'Message Checksum',
value: '0x' + ('0000' + decodeResult.raw.checksum.toString(16)).slice(-4),
value: '0x' + (decodeResult.raw.checksum >>> 0).toString(16).padStart(4, '0'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Apply Prettier formatting to this expression.

ESLint reports a prettier/prettier error on this line; wrap the value expression so the lint check passes.

Proposed fix
-      value: '0x' + (decodeResult.raw.checksum >>> 0).toString(16).padStart(4, '0'),
+      value:
+        '0x' + (decodeResult.raw.checksum >>> 0).toString(16).padStart(4, '0'),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
value: '0x' + (decodeResult.raw.checksum >>> 0).toString(16).padStart(4, '0'),
value:
'0x' + (decodeResult.raw.checksum >>> 0).toString(16).padStart(4, '0'),
🧰 Tools
🪛 ESLint

[error] 297-297: Insert ⏎·······

(prettier/prettier)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/utils/result_formatter.ts` at line 297, Format the value expression in
the result formatter object according to Prettier, wrapping the concatenation
and conversion expression as needed so the prettier/prettier lint check passes;
preserve the existing checksum formatting behavior.

Source: Linters/SAST tools

expect(item.value).toBe('0x003b');
});

test('preserves full hex for 20-bit checksum (no truncation)', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are there any examples of real messages that have this length checksum?

expect(item.value).toBe('0x123456');
});

test('displays large 32-bit CRC without sign artifacts', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same question

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.

Bug: ResultFormatter.checksum silently truncates checksums wider than 16 bits — displays wrong hex value

3 participants