Location: j1939/pgn.go:77 (DecodeID), j1939/pgn.go:91 (EncodeID)
Finding: A J1939 PGN is an 18-bit value where the Data Page (DP) bit occupies PGN bit 16, with bit 17 reserved for the Extended Data Page (EDP). DecodeID builds the PGN with uint32(dp)<<17, and EncodeID reads it back with (pgn >> 17) & 0x01. Both put the Data Page into the EDP slot instead of the DP slot. The 29-bit CAN arbitration ID itself is unaffected — DP still lands on CAN bit 24 as it should — and encode/decode are internally self-consistent, which is exactly why this doesn't show up as a decode/encode mismatch. But for every DP=1 message, the j1939.PGN value this library hands to callers is off by 0x10000 from the real PGN (e.g. the real DP=1 PGN 0x1F004 comes out as 0x2F004 here). Anyone constructing a j1939.Frame from a literal PGN taken off a standard PGN table, or comparing a decoded PGN against one, gets the wrong answer for the entire DP=1 address space, and interop with a real J1939 stack for any DP=1 traffic breaks.
Recommendation: Shift the DP bit by 16 instead of 17 in both DecodeID and EncodeID. If EDP support is wanted as a separate feature, decode CAN bit 25 into PGN bit 17 explicitly rather than reusing the DP shift. Add DP=1 test vectors (see companion finding on test coverage).
Filed from the 2026-07-29 ecosystem audit register; independently re-verified against current HEAD before filing.
Location:
j1939/pgn.go:77(DecodeID),j1939/pgn.go:91(EncodeID)Finding: A J1939 PGN is an 18-bit value where the Data Page (DP) bit occupies PGN bit 16, with bit 17 reserved for the Extended Data Page (EDP).
DecodeIDbuilds the PGN withuint32(dp)<<17, andEncodeIDreads it back with(pgn >> 17) & 0x01. Both put the Data Page into the EDP slot instead of the DP slot. The 29-bit CAN arbitration ID itself is unaffected — DP still lands on CAN bit 24 as it should — and encode/decode are internally self-consistent, which is exactly why this doesn't show up as a decode/encode mismatch. But for every DP=1 message, thej1939.PGNvalue this library hands to callers is off by 0x10000 from the real PGN (e.g. the real DP=1 PGN 0x1F004 comes out as 0x2F004 here). Anyone constructing aj1939.Framefrom a literal PGN taken off a standard PGN table, or comparing a decoded PGN against one, gets the wrong answer for the entire DP=1 address space, and interop with a real J1939 stack for any DP=1 traffic breaks.Recommendation: Shift the DP bit by 16 instead of 17 in both
DecodeIDandEncodeID. If EDP support is wanted as a separate feature, decode CAN bit 25 into PGN bit 17 explicitly rather than reusing the DP shift. Add DP=1 test vectors (see companion finding on test coverage).Filed from the 2026-07-29 ecosystem audit register; independently re-verified against current HEAD before filing.