Skip to content

fix(mqtt,nats): align generated wire protocols with the agreed ApiGear scheme - #11

Merged
w4bremer merged 4 commits into
mainfrom
fix-mqtt-nats-wire-protocol
Jun 5, 2026
Merged

fix(mqtt,nats): align generated wire protocols with the agreed ApiGear scheme#11
w4bremer merged 4 commits into
mainfrom
fix-mqtt-nats-wire-protocol

Conversation

@w4bremer

@w4bremer w4bremer commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

The generated Rust MQTT and NATS adapters had diverged from the agreed ApiGear wire scheme that the C++17/Qt/Python templates implement, so a Rust client/service could not interoperate with them over the same broker. This aligns both transports with the spec (verified against the C++17 code, not the PDFs — the NATS PDF's swapped property verbs are a known documentation error).

MQTT

  • Migrate the adapters to rumqttc's MQTT 5 client. This is required: the peer routes RPC replies via the MQTT 5 ResponseTopic + CorrelationData properties.
  • Operations use rpc/<op>; the client now correlates and awaits the reply (previously it published op/<op>/req and silently dropped the response).
  • Drop the apigear/ prefix; split the single prop/<p> into set/<p> (change request) and retained prop/<p> (notification); drop the bespoke state topic (retained prop/ carries the state).

NATS

  • Drop the apigear. prefix; operations use rpc.<op> (was op.<op>).
  • Split prop.<p> into set.<p> (request) and prop.<p> (notification) — this also removes a service-side self-echo.
  • Announce service.available and answer the init handshake on init.resp.<clientId> (numeric id) instead of the bespoke state subject.

Core

  • The per-interface *Data state structs now serialize with the IDL property names (#[serde(rename = "...")]), so the camelCase wire keys actually deserialize. Previously the snapshot silently fell back to defaults (affected NATS init/state and OLink).

Resulting wire scheme (tb.simple / SimpleInterface)

MQTT (/) NATS (.)
operation …/rpc/<op> (+ MQTT5 ResponseTopic/CorrelationData) …​.rpc.<op> (request/reply)
property set …/set/<p> …​.set.<p>
property notify …/prop/<p> (retained) …​.prop.<p>
signal …/sig/<p> …​.sig.<p>
availability/init retained props …​.service.available + …​.init…​.init.resp.<id>

Verification

  • cargo fmt --check, clippy -D clippy::correctness -D clippy::perf, cargo doc -D warnings: clean
  • goldenmaster matches a clean regeneration (go run main.go diff): clean
  • 524 unit tests + 46 broker round-trip tests (MQTT/NATS/OLink): green
  • Cross-technology interop verified against the real C++17 example binaries over a live broker, in all four directions — Rust client ↔ C++ server and C++ client ↔ Rust server, for both MQTT and NATS. The C++ client reads the Rust server's seeded propInt/propString, and the Rust client's RPC round-trips against the C++ server.

Commits

  • fix(core): serialize interface state with IDL property names
  • fix(nats): align generated wire protocol with the agreed ApiGear scheme
  • fix(mqtt): align generated wire protocol with the agreed ApiGear scheme
  • docs: update MQTT/NATS feature docs for the corrected wire scheme

w4bremer added 4 commits June 5, 2026 11:15
The per-interface `*Data` structs (used to deserialize the full property
snapshot for NATS init/state and OLink) had snake_case field names but no
serde rename, so they could not parse the camelCase IDL keys the wire
actually carries (e.g. `propInt`). State snapshots silently fell back to
default values. Add `#[serde(rename = "<IDL name>")]` per field so the keys
match the wire and the other ApiGear templates.
The NATS adapters diverged from the scheme used by the C++/Qt/Python
templates. Bring them in line so a Rust client/service interoperates over
the same nats-server:

- subjects: drop the `apigear.` prefix; operations use `rpc.<op>` (was
  `op.<op>`); split the single `prop.<p>` into `set.<p>` (client->service
  request) and `prop.<p>` (service->client notification), which also removes
  a self-echo on the service.
- availability/state: announce `service.available` and answer the `init`
  handshake on `init.resp.<clientId>` instead of the bespoke `state`
  subject. The client id is a number, matching the other templates.
Bring the MQTT adapters in line with the C++/Qt/Python templates so a Rust
client/service interoperates over the same broker:

- migrate the adapters to rumqttc's MQTT 5 client; operations use
  `rpc/<op>` with the MQTT 5 ResponseTopic + CorrelationData properties and
  the reply is correlated and awaited (previously the client published an
  `op/<op>/req` request and silently dropped the response).
- drop the `apigear/` prefix; split the single `prop/<p>` into `set/<p>`
  (change request) and retained `prop/<p>` (notification); drop the bespoke
  `state` topic — retained `prop/` messages carry the current state.
Document the agreed topic/subject scheme (rpc/set/prop/sig, MQTT 5
correlation, NATS service.available + init handshake) and the updated
adapter API (rumqttc v5, client id constructor argument).
@w4bremer
w4bremer merged commit 1d580df into main Jun 5, 2026
7 checks passed
@w4bremer
w4bremer deleted the fix-mqtt-nats-wire-protocol branch June 5, 2026 09:28
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.

1 participant