Repository navigation
feat: v2.19.0 — wire format conformance fix pass (acf/avtp/e2e) - #94
Merged
Merged
Conversation
… 63) Re-derives rcp/avtp.hpp's NTSCF/TSCF header bit-packing and rcp/acf.hpp's ACF_ABB/ACF_GBB Message Info bit-packing field by field from the OPEN Alliance TC18 Remote Control Protocol Specification v0.5.1_RC's own bit-position diagrams, replacing the self-admitted "not full bit-for-bit wire conformance" placeholder encoding landed at milestone 44 (v2.0.0). - avtp.hpp: NTSCF is 12 bytes (was 14), TSCF is 24 bytes (was 18); sequence_num is an 8-bit rolling counter (was 16-bit); ntscf_data_length is an 11-bit sub-octet field; TSCF's stream_data_length lives in a dedicated 4-byte word after a previously-missing 4-byte reserved word. ByteBusId widens uint8_t -> uint16_t (byte_bus_id is an 11-bit wire field). - acf.hpp: the Message Info header is 8 bytes (was 10), matching the specification's own two worked CRC examples that label it "8 byte Message Info"; acf_msg_type/acf_msg_length share a byte (7+9 bits), byte_bus_id is 11 bits, evt is a 4-bit field, read_size_or_segment_num is 12 bits. Fixes two real dispatch bugs this exposed elsewhere in the tree (rcp/udp.hpp, rcp/record.hpp compared a raw wire byte against kAcfMsgTypeAbb/kAcfMsgTypeGbb directly, which almost never matches once acf_msg_type shares byte0 with acf_msg_length's MSB) via a new peek_acf_msg_type() helper. Also corrects make_response/response_kind_of: every response (including Acknowledge) carries rsp=1 per the specification's own response table; Acknowledge is distinguished by evt[3:0] == 0xF, not by rsp being clear. - e2e.hpp: coverage_buffer/compute_crc/verify_crc gain a message_timestamp parameter and include it, at the correct byte offset, in an ACF_GBB message's CRC coverage (previously omitted entirely). E2eErrc::crc_error gains a numeric TC18 wire error code accessor (POCI_FAILURE = 12). - acf.hpp gains ControlFlags/Message, an additive, documented thin view over AcfMessageInfo matching RELAY spec §15.5's canonical decoded-frame shape. Closes #73, #66, #76, #89. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
…tches Merges origin/main (which now includes the retired-model-removal and wire-format-endpoint-fix PRs) into this branch. ROADMAP.md conflicted because both this branch and the retired-model-removal PR appended a milestone entry after #62; resolved by keeping both narratively in order (dated cleanup entry, then '63. Wire Format Conformance Fix Pass', then '64. TC18 RCP GA', renumbered from '63'). Two integration-only defects surfaced by combining this branch's avtp::ByteBusId widening (uint8_t -> uint16_t, an 11-bit wire field) with code neither this branch nor the retired-model-removal branch touched on its own: - tests/test_rcp.cpp (added by the retired-model-removal PR, which predates this branch's ByteBusId widening) asserted ByteBusId == uint8_t via static_assert; updated to assert the type is wide enough for the real 11-bit range instead. - include/rcp/discovery.hpp's encode_discovery_request took sequence_num as uint16_t and assigned it into NtscfHeader::sequence_num, which this branch narrowed to uint8_t (sequence_num is an 8-bit wire field, not 16) as part of the same header fix. Neither this file nor its test was in this branch's original diff, so the mismatch only appeared once merged; narrowed the parameter type to match. Full local build (C++17, Ninja) and ctest (52/52 tests) pass on the merged tree. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
test_capi.cpp's mock_server_request_fn assigns AcfMessageInfo::byte_bus_id (now avtp::ByteBusId = uint16_t, an 11-bit wire field) into rcp_response_t::info.byte_bus_id (rcp_byte_bus_id_t = uint8_t, a pre-existing, narrower C-ABI type unrelated to and out of scope for this fix pass). GCC/Clang silently truncate; MSVC's warnings-as-errors build fails on the implicit narrowing (C4244 -> C2220). Add an explicit cast with a comment explaining the pre-existing ABI width gap, rather than changing capi.h's public C ABI. 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.
Summary
Fixes
include/rcp/acf.hppandinclude/rcp/avtp.hpp's self-admitted "not full bit-for-bit wire conformance" placeholder bit-packing (landed at milestone 44, v2.0.0) by re-deriving the NTSCF/TSCF AVTPDU headers and the ACF_ABB/ACF_GBB "Message Info" common header field-by-field from the OPEN Alliance TC18 Remote Control Protocol Specification v0.5.1_RC's own bit-position diagrams. Also fixesinclude/rcp/e2e.hpp's CRC coverage (was silently omitting ACF_GBB'smessage_timestampfrom the covered bytes) and adds a numeric TC18 wire error code forE2eErrc::crc_error, plus an additive RELAY spec §15.5 canonicalControlFlags/Messagetype.Closes #73 (cpp-RCP-04), #66 (cpp-RCP-N2-03), #76 (cpp-RCP-08), #89 (cpp-RCP-FS-06).
How the bit layout was derived
The specification's field diagrams (Figures 5-8, page 22-25) are pixel raster images with a per-bit ruler, not extractable text, so I rendered the relevant pages at 300-600 DPI (
pdftoppm), cropped/zoomed with Pillow, and counted tick marks against the ruler numerically (measuring divider pixel positions and dividing by the page's own px-per-bit spacing) rather than eyeballing. Every field width below was counted this way, not assumed from generic IEEE 1722 defaults — the task brief specifically warned this protocol customizes the standard AVTP/ACF headers, and that warning turned out to be accurate (see below).NTSCF header (12 bytes, was 14):
subtype(8)sv(1)version(3, always 0)r(1, reserved)ntscf_data_length(11, split across the last 3 bits of byte1 + all of byte2)sequence_num(8)stream_id(64).TSCF header (24 bytes, was 18):
subtype(8)sv(1)version(3)mr(1)rsv(2)tv(1)sequence_num(8)reserved(7)tu(1)stream_id(64)avtp_timestamp(32)reserved(32)stream_data_length(16)reserved(16). The old 18-byte implementation was missing an entire 4-byte reserved word and hadsequence_num/timestamp-valid in the wrong bit positions (tvwas previously at0x40, i.e. one bit off from its real position at0x01).ACF Message Info (8 bytes, was 10):
acf_msg_type(7, not a whole octet)acf_msg_length(9, sharing a byte boundary withacf_msg_type)pad(2)mtv(1)rsv(2)byte_bus_id(11)evt(4)rsv(2)hs(1)cs(1)transaction_num(8)op(1)rsp(1)err(1)ms(1)read_size_or_segment_num(12).Cross-checks performed (no independent TC18 implementation was available to interop-test against directly):
acf_msg_lengthvalues (0x05for a 6-byte-payload+2-pad ACF_ABB,0x07for a 7-byte-payload+1-pad ACF_GBB) and explicitly label the header "8 byte Message Info." My own quadlet-counting logic (2 quadlets header + 2 quadlets padded payload + 1 quadlet CRC trailer = 5; and the GBB equivalent = 7) reproduces both stated values exactly, and the 8-byte total matches the header byte count I derived from the figures independently.SoundMatt/go-RCP) as a possible third cross-check, per the task's nice-to-have suggestion. Itsavtp/acfpackages turned out to carry the identical "not a verified transcription of the published byte assignments, own reasoned encoding" disclaimer this PR removes from cpp-RCP — including a different, self-admittedly-arbitrary TSCF subtype byte (0x83, vs. the0x05I derived from the actual spec figure) and a fully byte-aligned (not bit-packed) message layout. So go-RCP was not usable as an independent bit-layout cross-check here; this is a negative but informative result — go-RCP's own ACF/AVTP wire layer needs the same treatment this PR gives cpp-RCP, and I did not change go-RCP (out of scope, per instructions — never edit other repos in this remediation effort). One positive cross-check did come out of it: go-RCP'sControlFlagsbit values (FlagAck=0x80, etc.) match RELAY spec §15.5 exactly, same as what I implemented for [cpp-RCP-FS-06] Canonical ControlFlags type and Message frame struct are absent #89, which corroborates both were correctly transcribed from that spec text (RELAY spec §15.5, not TC18, is the authority for those specific bit values).Honest confidence level
High confidence, not independently interop-verified. Every bit position was counted (not eyeballed) from the specification's own diagrams and cross-checked against a second in-spec presentation of the same header shapes plus two fully worked numeric examples, all of which agree. I do not claim "full bit-for-bit wire conformance against other TC18 implementations" as an established fact — there is no independent TC18 implementation in this environment to round-trip a frame against, and go-RCP (the one candidate) turned out to have the same unverified-encoding problem, not a working reference. If a real interoperability test against another vendor's TC18 stack ever surfaces a mismatch, the two places most likely to be wrong are (a) something in the two-per-week-old sub-octet fields I had no second textual confirmation for at all —
mr/tu/r/the four reserved-word fields, none of which have documented RCP-specific behavior, so I left them un-exposed and always-zero rather than guessing a meaning — and (b)evt's exact interpretation in the "standard request" context, where the spec text itself says usage is "endpoint specific, see section 13.5" (a section I did not need to read for this task's scope).Verification performed
cmake -B build -DCMAKE_BUILD_TYPE=Debug -G Ninja && cmake --build build --parallel— clean build, zero warnings, across the whole tree (GCC 16 locally; also spot-checked with AppleClang, which caught one thing GCC didn't — see below).ctest --test-dir build --output-on-failure— 53/53 test binaries pass, including new round-trip and hand-computed-vector tests added totests/test_acf.cpp,tests/test_avtp.cpp,tests/test_e2e.cpp.clang-tidy(Homebrew LLVM 22,clang-diagnostic-*,clang-analyzer-*) run againsttest_acf.cpp/test_avtp.cpp/test_e2e.cppwith a--header-filterscoped to my three touched headers: 0 warnings after two fixes (see below). Note: this repo has no.clang-tidyconfig file and CI's ownclang-tidyjob is soft-fail (|| true), so this was a best-effort local check, not a repo-standard gate.-Wall -Wextradidn't: a pre-existing test line intest_avtp.cppsetsequence_num = 4242for what is now (correctly) an 8-bit field — fixed to242.clang-analyzer-optin.core.EnumCastOutOfRangeflagged the newControlFlagsbitmaskoperator|/operator&(an OR of two single-bit flags legitimately produces a combined value with no enumerator of its own) — this is a known false-positive pattern for bitmask enums;NOLINT'd with an explanatory comment rather than silently ignored.Files touched outside acf.hpp/avtp.hpp/e2e.hpp, and why
Per the task brief's explicit allowance to make minimal mechanical fixes at call sites when a wire-format fix changes behavior:
include/rcp/udp.hpp(decode_frame) andinclude/rcp/record.hpp(read_msg): both compared a raw wire byte directly againstacf::kAcfMsgTypeAbb/kAcfMsgTypeGbbto decide which decoder to call. That comparison was only ever correct because the old (wrong) layout happened to putacf_msg_typein a whole octet; now thatacf_msg_typeis a real 7-bit field sharing byte0 withacf_msg_length's MSB, that raw comparison would essentially never match. Fixed both to go through a newacf::peek_acf_msg_type()helper instead. This is a real, would-have-shipped-broken bug the layout fix exposed, not a style change.tests/test_udp.cpp: one line building astd::vector<uint8_t>from a now-uint16_tbyte_bus_idvia a braced-init-list triggered a new (GCC-silent, AppleClang-loud) narrowing warning; added an explicitstatic_cast<uint8_t>with a one-line comment. Purely mechanical, no behavior change (the endpoint IDs involved were always < 256)..fusa-reqs.json/ROADMAP.md: addedREQ-WIRE-015for the new additiveControlFlags/Messagetypes ([cpp-RCP-FS-06] Canonical ControlFlags type and Message frame struct are absent #89) and a new milestone 63 (v2.19.0) ahead of the existing "TC18 RCP — General Availability" milestone (renumbered 63→64), perCONTRIBUTING.md's checklist.Left unresolved / explicitly out of scope
mr,tu, the NTSCFrbit, and the two 32-bit reserved TSCF words have no documented RCP-specific behavior found anywhere in the spec text I read for this task; they're encoded as always-zero and not exposed as struct fields (same "don't invent semantics" posture used elsewhere in this codebase, e.g. the IEEE1722svbit).evtusage for standard requests) was out of this task's scope;evt_ack/evt_opare kept as the existing split (now correctly bit-packed as a real 4-bitevtnibble) rather than redesigned, to avoid a large, unnecessary blast radius acrossmock.hpp/cli.hpp/adapt.hpp, none of which needed a source change for this fix.acf.hpp/avtp.hppheader comments remain out of scope here too.Branch:
fix/acf-avtp-wire-conformanceConfidence in bit-layout correctness: High (derivation + independent in-spec cross-check + two worked numeric examples all agree); not independently interop-tested against another TC18 implementation — none was available.