midge implements OASIS MQTT version 3.1.1. This document tracks what's
covered, what's deliberately deferred, and why - docs/ARCHITECTURE.md
covers the code layout this lives in.
src/core/mqtt_packet.c encodes all 14 packet types. mqtt_decode()
only decodes the types a client actually receives from a broker:
| Type | Encode | Decode |
|---|---|---|
| CONNECT | yes | n/a (client -> broker only) |
| CONNACK | n/a | yes |
| PUBLISH | yes | yes |
| PUBACK | yes | yes |
| PUBREC | yes | yes |
| PUBREL | yes | yes |
| PUBCOMP | yes | yes |
| SUBSCRIBE | yes | n/a (client -> broker only) |
| SUBACK | n/a | yes |
| UNSUBSCRIBE | yes | n/a (client -> broker only) |
| UNSUBACK | n/a | yes |
| PINGREQ | yes | n/a (client -> broker only) |
| PINGRESP | n/a | yes |
| DISCONNECT | yes | n/a (client -> broker only) |
A client never needs to parse the packet types it only ever sends, so
mqtt_decode() rejects them as malformed rather than carrying unused
parsing paths; their encode-side correctness is instead covered by
byte-exact vectors (tests/vectors.h, built by hand from the spec) in
tests/test_codec.c.
mqtt_decode() is a tolerant receiver by default (accepts anything the
spec doesn't explicitly forbid), with a small, deliberate set of
exceptions where a violation is rejected outright as -MQTT_ERR_MALFORMED
rather than surfaced to the caller:
- MQTT-4.7.3-1: a PUBLISH topic name must not be empty.
- MQTT-3.3.1-2: DUP must be 0 on a QoS 0 PUBLISH. A malformed/ misbehaving broker (or a MITM'd packet) setting it anyway is rejected rather than passed through with a DUP flag the QoS 0 delivery path has no defined meaning for.
Other spec requirements not on this list are intentionally left unenforced for now (a tolerant-receiver posture, not an oversight) - see issue tracker for anything still under discussion.
- QoS 0 is fully supported both directions.
- QoS 1 subscribe/receive is supported:
mqtt_client_process()auto-acknowledges an incoming QoS 1 PUBLISH with a PUBACK. QoS 1 publish (outbound) stays unsupported at thesrc/corelevel -mqtt_client_publish()only ever sends QoS 0, deliberately: the retransmission-on-timeout and dedup state QoS 1 needs doesn't belong in an allocation-free, stateless-beyond-one-connection core (seedocs/ARCHITECTURE.md's layering rules). That state now lives inmqtt.library's connection subprocess instead (Phase 2, shipped) -MQTT_Publish()supports QoS 1 with DUP retransmission, and the default, library-linkedmqtt_pubexposes it viaQOS 1. The static build (mqtt_pub-static, linkingsrc/coredirectly with no subprocess to hold that state) still only supports QoS 0 publish and rejectsQOS 1with a message explaining why - seeuserdocs/CLI-Reference.md's "Two build flavours" section. - QoS 2 is out of scope entirely, in this release and for the
foreseeable roadmap. It roughly doubles the state machine (a 4-packet
handshake with its own dedup/retry rules) for a guarantee
(exactly-once) that essentially no smart-home broker/device pairing
actually needs - QoS 1 (at-least-once, deduplicated at the
application level if it matters) covers every realistic use case this
project targets.
mqtt_encode_publish(),mqtt_encode_subscribe(), and the CONNECT will builder all rejectqos == 2withMQTT_ERR_PROTOCOLrather than silently downgrading it.
mqtt_client_process() schedules a PINGREQ once 3/4 of the keepalive
interval has elapsed since the client last sent anything, and treats a
missing PINGRESP after a full keepalive interval as a fatal connection
error (MQTT_CLIENT_ERR_KEEPALIVE_TIMEOUT) - the same 3/4 convention
common MQTT client libraries use, giving one full round-trip of slack
before the broker's own keepalive×1.5 grace period (spec section 3.1.2.10)
would drop the connection first. A CONNECT that never gets a CONNACK within
one keepalive period is treated the same way
(MQTT_CLIENT_ERR_CONNECT_TIMEOUT).
One caveat, documented in src/core/mqtt_client.h: mqtt_client_publish()
and mqtt_client_subscribe() don't take a clock argument (matching the
approved implementation plan), so they don't reset the "last sent"
timer - only mqtt_client_connect() and mqtt_client_process()'s own
PINGREQ do. In practice this means a PINGREQ may go out slightly earlier
than the theoretical minimum, never later, so it doesn't threaten the
timeout guarantee above.
All of the above assumes now_ms is monotonic. On the host it is
(CLOCK_MONOTONIC); on the Amiga it is not (DateStamp() - see
src/tools/tool_clock.h's own comment and issue #8) - a clock change
mid-session there can skew or spuriously trip any of these timeouts.
Deliberately deferred: a real fix needs a genuinely monotonic source
(timer.device), which would need per-connection state threaded through
every caller of what is otherwise a stateless clock function, for a
failure mode (something actively changing the clock mid-session) that's
rare in practice.
mqtt_client_connect() always sets Clean Session (bit 1 of the CONNECT
flags). Persistent sessions (broker-retained subscriptions and queued QoS 1
messages across reconnects) are part of the Phase 2 reconnect/resubscribe
work, not this release.