Repository navigation
fix: v2.22.0 — ACF_GBB message_timestamp is spliced between the Message Info header's two quadlets - #100
Merged
Conversation
…r quadlets encode_acf_gbb/decode_acf_gbb placed the 64-bit message_timestamp after the complete 8-byte shared Message Info header (wire octet 8). TC18 splices it between that header's two 4-byte quadlets (wire octet 4), which pushes evt/rsv/hs/cs/transaction_num/op/rsp/err/ms/read_size_or_segment_num from octet 8 to octet 12. Total message length was unaffected, and this codec round-tripped against itself perfectly, which is why neither the v2.19.0 bit-packing pass nor the v2.20.0 acf_msg_length pass caught it — but every ACF_GBB message this library emitted was unreadable by a conformant peer, and every ACF_GBB E2E CRC was computed over the wrong byte sequence. ACF_ABB is unaffected: it has no timestamp field. Verified against the specification PDF's own figures (rendered at high DPI and read directly, not inferred from this codec) four independent ways: the single-ACF_GBB CRC-coverage figure's three-row "Byte Message Info" group (quadlet 0, 64-bit message_time_stamp, quadlet 1); that figure's own stated acf_msg_length 0x07 = 28 octets, which only closes as 4 + 8 + 4 + 8 + 4 (its ACF_ABB counterpart states 0x05 = 20 = 4 + 4 + 8 + 4); the compound-request figure, where the mtv=0 repurposing fields occupy octets 4..11 between the same two quadlets; and the response-field table, which lists message_timestamp between byte_bus_id and evt. - acf.hpp: new kAcfHeaderQuadletLen/kAcfGbbTimestampOffset/ kAcfGbbSecondQuadletOffset/kAcfGbbMessageInfoLen and encode_acf_gbb_message_info/decode_acf_gbb_message_info, which own the spliced byte geometry and delegate all bit packing to the existing encode/decode_acf_message_info. encode_acf_gbb, decode_acf_gbb and decode_acf_messages route through them. - e2e.hpp: coverage_buffer hand-serialized the same wrong order; it now calls acf::encode_acf_gbb_message_info, so the CRC-covered bytes equal the transmitted bytes by construction. - udp.hpp/l2.hpp/record.hpp needed no change (all route through the codec, no hardcoded offsets). request.hpp needed none either (it deals in the slot's value, not its position) and gained only a comment. - tests: test_acf.cpp and test_e2e.cpp had pinned the bug as correct by reading back the encoder's own output. Rewritten around literal spec-derived octet positions with the derivation spelled out, plus a new full-message conformance vector reproducing the CRC-coverage figure's message octet for octet and asserted in both directions. Mutation-checked: restoring the old offsets fails 4 test cases. - .fusa-reqs.json: REQ-WIRE-005/REQ-E2E-002 now state exact octet positions (REQ-E2E-002 also still said "10-byte ACF shared header", stale since v2.19.0 made it 8); REQ-SEQ-001 records where the repurposed slot sits. Wire-format-breaking for ACF_GBB only; no public API signature changes. kVersion/CMakeLists.txt 2.21.0 -> 2.22.0. Build clean, ctest 53/53. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
encode_acf_gbb/decode_acf_gbbplaced the 64-bitmessage_timestampafter the complete 8-byte shared Message Info header, at wire octet 8. TC18 splices it between that header's two 4-byte quadlets, at wire octet 4 — which pushes the entire second quadlet (evt/rsv/hs/cs/transaction_num/op/rsp/err/ms/read_size_or_segment_num) from octet 8 to octet 12.acf_msg_type/acf_msg_length/pad/mtv/rsv/byte_bus_idmessage_timestamp[63:32]message_timestamp[31:0]message_timestamp[63:32]evt/rsv/hs/cs/transaction_num/op/rsp/err/ms/read_sizemessage_timestamp[31:0]byte_msg_payloadbyte_msg_payload(same)The total message length was unaffected, the payload started at the same offset, and the codec round-tripped against its own decoder perfectly. Only the four bytes in the middle were in the wrong place — which is exactly why neither the v2.19.0 bit-packing re-derivation nor the v2.20.0
acf_msg_lengthpass caught it: both were auditing bit positions within the header and the length field, not the header's byte geometry around the timestamp.Consequences: every ACF_GBB message this library has ever emitted was unreadable by a conformant peer (and vice versa), and every ACF_GBB E2E CRC was computed over the wrong byte sequence. ACF_ABB is entirely unaffected — it has no timestamp field, so its two quadlets have always been correctly contiguous at octets 0..7.
How the correct layout was verified
Independently, and before any code changed, against the specification PDF itself — the relevant layouts exist only as figures, not as extractable text, so the pages were rendered at high DPI and read directly rather than trusting a text extraction or this codec's prior behavior. Four mutually independent confirmations:
acf_msg_type/acf_msg_length/pad/mtv/rsv/byte_bus_idquadlet; thenmessage_time_stampas a double-height 64-bit block (rendered exactly the way that same figure's 64-bitstream_idis drawn); then theevt/rsv/hs/cs/transaction_num/op/rsp/err/ms/read_sizequadlet.acf_msg_length=0x07for its worked example: 7 quadlets = 28 octets = quadlet0(4) + timestamp(8) + quadlet1(4) + payload(7 real + 1 pad = 8) + CRC32(4). No other placement of a 64-bit timestamp closes that sum. Its ACF_ABB counterpart states0x05= 20 octets = 4 + 4 + (6 real + 2 pad = 8) + 4, confirming the same two quadlets with nothing between them.mtv=0) shows the fields that repurpose the timestamp slot —request_type/cmp_start_state/cmp_next_state/cmp_sequencer, thencmp_exec_delay/cmp_repetitions— occupying exactly the two quadlets between the same two header quadlets, i.e. octets 4..11. That figure also independently confirmsrcp/request.hpp's existing byte assignment within the slot (opcode in the slot's first octet, seven parameter octets after), which needed no change.message_timestamp("Present in ACF_GBB, omitted in ACF_ABB") betweenbyte_bus_id— quadlet 0's last field — andevt— quadlet 1's first field.What changed
include/rcp/acf.hpp— newkAcfHeaderQuadletLen/kAcfGbbTimestampOffset/kAcfGbbSecondQuadletOffset/kAcfGbbMessageInfoLen, documented with the point-by-point verification above, plusencode_acf_gbb_message_info/decode_acf_gbb_message_info. These own the ACF_GBB-specific byte geometry and delegate all bit-level packing to the existingencode_acf_message_info/decode_acf_message_info, so the bit layout keeps exactly one definition in the file.encode_acf_gbb,decode_acf_gbband the multi-message walkerdecode_acf_messagesall route through them.include/rcp/e2e.hpp—coverage_bufferserialized the ACF_GBB Message Info block by hand (header, then timestamp) and inherited the same wrong order. It now callsacf::encode_acf_gbb_message_info, making the CRC-covered bytes byte-identical to the transmitted bytes by construction rather than by two code paths happening to agree. A new assertion pins that identity directly.include/rcp/udp.hpp,include/rcp/l2.hpp,include/rcp/record.hpp— no change needed. All three already route every ACF_GBB message throughencode_acf_gbb/decode_acf_gbband hardcode no offsets of their own; grepped and confirmed. They inherit the fix.include/rcp/request.hpp— no functional change needed either: it deals only in the value of the 64-bit slot, never its wire position. Gained a comment recording that, plus confirmation (3) above of its within-slot byte assignment..fusa-reqs.json—REQ-WIRE-005andREQ-E2E-002now state the exact octet positions rather than leaving the geometry implicit. (REQ-E2E-002also still said "the 10-byte ACF shared header", stale since v2.19.0 made it 8.)REQ-SEQ-001records where the repurposed slot actually sits.ROADMAP.md/README.md— new milestone 66 with the full derivation; GA renumbered 66 → 67; the summary table gains rows for v2.21.0 (previously missing) and v2.22.0; milestone 63's stale "at the correct byte offset" claim about CRC coverage corrected in place.Tests
tests/test_acf.cppandtests/test_e2e.cpphad pinned the bug as correct — the classic tautology failure of asserting an encoder against its own output. Rewritten around literal octet positions derived from the figures above, with the derivation written out in the test files themselves:test_acf.cpp's layout comment now documents both message types' octet positions and the arithmetic cross-check, and the ACF_GBB byte-sequence case asserts one whole hand-written literal frame, then re-asserts field by field at literal offsets, then decodes the literal back to fields — the direction that proves the decoder handles spec-shaped bytes rather than merely its own encoder's bytes. Two explicit regression assertions (frame[4] == 0x11,frame[8] == 0x55) discriminate the two layouts directly.test_e2e.cppgains a full-message conformance vector reproducing the CRC-coverage figure's own message field for field (acf_msg_type0x0D,acf_msg_length0x07,pad1, 7+1-octet payload, CRC32 trailer, 28 octets total), asserted octet for octet in both directions; and itscoverage_buffercase now asserts the covered bytes equalencode_acf_gbb's output exactly.Mutation-checked: reverting the two offset constants to their pre-fix values (4 → 8, 12 → 4) fails 4 test cases across the two suites. The new expectations genuinely discriminate between the two layouts rather than restating whatever the encoder does.
Full local build clean,
ctest53/53 suites pass (macOS/arm64, GCC 16, C++17). Thel2-veth-roundtripprogram is Linux-only (AF_PACKET) and was not runnable in this environment; its CI job covers it. The UDP round-trip suite (rcp_udp) runs and passes here, exercising the GBB path end to end over real sockets.Breaking change
This changes bytes on the wire for every ACF_GBB message, so a v2.22.0 peer cannot interoperate with a pre-v2.22.0 peer for GBB traffic. ACF_ABB traffic is unaffected. No public API signature, struct field, or function name changes —
AcfMessageInfoand theencode_acf_gbb/decode_acf_gbbsignatures are identical — so this is source-compatible and wire-incompatible.Since the pre-v2.22.0 bytes were never conformant with anything but themselves, correcting them is strictly a move toward the interoperability GA requires.
kVersion/CMakeLists.txtbump 2.21.0 → 2.22.0, following the same minor-bump convention v2.19.0, v2.20.0 and v2.21.0 used for their own wire-format corrections during this pre-GA v2.x line.