Skip to content

qml: fix the Protocols.* bindings a manual QML corpus exercises - #939

Merged
jcelerier merged 2 commits into
masterfrom
fix/qml-protocol-transport-defects
Sep 14, 2026
Merged

jcelerier merged 2 commits into
masterfrom
fix/qml-protocol-transport-defects

Conversation

@jcelerier

Copy link
Copy Markdown
Member

A corpus of manual QML protocol scenarios, run against real sockets from score's Mapper device, turns up a number of defects in the Protocols.* bindings. Each one below is covered by an integration test in the companion score PR, and each was proven by reverting the fix and watching that test go red.

Framing and transports

  • Coalesced line framing dropped every line but the first. line_framing_decoder::receive() cleared the shared read buffer, but async_read_until reports the delimiter without consuming what follows it, so the rest of a coalesced read was destroyed. Now every complete line of a read is dispatched in order and a trailing partial line is kept for the next one.
  • Line-framed sockets never observed EOF: the completion handler returned on ec.failed() before validate_stream, so the framing most hardware protocols use could not report a close. EOF now routes through validate_stream without re-arming a dead socket.
  • An Intel HEX / S-record EOF record was dispatched as an empty message, overwriting the last decoded payload and echoing an empty frame back.
  • A client never saw the peer close unless the script had a message callback: the read loop was only armed then. It is now always armed (payloads still only dispatched when a callback exists), an abortive close is reported like a graceful one, and on_close is emitted at most once.
  • onFail was dead. The documentation block advertises it; every factory read only onError. It is now a documented alias.

WebSocket

  • inboundWS did not listen: it allocated a state object, called onOpen and returned. It now runs a real server on the shared asio context, with a per-connection object, and releases the listener on destruction.
  • The WebSocket client invoked its QJSValue callbacks straight from the websocketpp thread; they now hop to the object's Qt thread like everything else.

HTTP

There were two client implementations; the legacy Protocols.http(url, cb, verb) used the older one, which dropped the query string, sent a hard-coded Host, silently discarded every non-2xx response and had a no-op error path. It is now an adapter over the single implementation, which additionally learned: bodiless responses (HEAD, 1xx, 204, 304, 2xx-to-CONNECT) complete at end of headers, a body truncated before Content-Length is an error rather than a success, Content-Length is an upper bound, and the authority is built correctly (port when non-default, brackets for IPv6, punycode for IDN, userinfo, CRLF rejected in header values).

MIDI / UMP

port_information::api was not serialized, so an endpoint that round-tripped through QML came back as API(0) and libremidi refused to open it. It now round-trips, and an endpoint without a usable API reports why.

Mapper scripting

  • An unqualified Device.write("/leaf") resolved against every device in the document and could land on a sibling; it now resolves in the script's own device, while name:/path still reaches any.
  • Sockets opened during QML property evaluation ran onOpen before the Mapper's tree existed, so the Device.write they made was silently dropped. Opening is deferred to the event loop.

Build

Every target that compiles boost.asio in one binary must agree on BOOST_ASIO_ENABLE_BUFFER_DEBUGGING; libremidi did not, which is an ODR violation in Debug and shows up under ASan as a new-delete-type-mismatch on the resolver thread.

The Protocols.* bindings had a number of defects that a corpus of manual
QML scenarios makes visible as soon as they are run against real sockets:

- line framing dropped every complete line after the first of a coalesced
  read, and never routed EOF through validate_stream, so a line-framed
  socket could not observe a remote close;
- an Intel HEX / S-record EOF record, which decodes to no payload, was
  dispatched as an empty message;
- TCP, Unix-stream and serial sockets only armed their read loop when the
  script had a message callback, so a client without one never saw the
  peer close; an abortive close was never reported at all;
- onFail, which the documentation advertises, was never read: only
  onError was;
- inboundWS did not listen: it allocated a state object, called onOpen
  and returned;
- the WebSocket client invoked its QJSValue callbacks straight from the
  websocketpp thread;
- the HTTP client had two implementations, the legacy one dropping the
  query string, sending a made-up Host, and reporting neither non-2xx
  responses nor errors; both mishandled HEAD, bodiless responses,
  interim 1xx replies, truncated bodies and Content-Length as an upper
  bound;
- MIDI/UMP endpoints could not be reopened after a round-trip through
  QML, since port_information::api was not serialized;
- an unqualified Device address resolved against every device of the
  document rather than the script's own;
- sockets opened during QML property evaluation ran their onOpen before
  the Mapper's tree existed, silently dropping the Device.write they made.

Also make every target that compiles boost.asio agree on
BOOST_ASIO_ENABLE_BUFFER_DEBUGGING: libremidi did not, which is an ODR
violation in a Debug build.
effective_type() returns what the port declares and falls back to the
address, but the case covering a port that declares a unit still required
the address's one, so the suite went red on master.
@jcelerier
jcelerier force-pushed the fix/qml-protocol-transport-defects branch from 6a68086 to 614d49c Compare September 14, 2026 05:21
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier deployed to Apple Certificate September 14, 2026 05:21 — with GitHub Actions Active
@jcelerier
jcelerier merged commit 05f3cc7 into master Sep 14, 2026
36 of 42 checks passed
@jcelerier
jcelerier deleted the fix/qml-protocol-transport-defects branch September 14, 2026 12:53
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