eclipse / paho.mqtt.rust

paho.mqtt.rust
Other
516 stars 102 forks source link

MQTT v5 Issue #160

Closed toddpenland closed 1 year ago

toddpenland commented 2 years ago

When setting the MQTT version to 5, it appears that the flag for [cleansession] (https://github.com/eclipse/paho.mqtt.rust/blob/e9e4b804b56f2731af9c0ecf4712d5f5871c25ba/src/connect_options.rs#L486) should be set to 1.

Based on the comments for [clean_start] (https://github.com/eclipse/paho.mqtt.rust/blob/e9e4b804b56f2731af9c0ecf4712d5f5871c25ba/src/connect_options.rs#L334) and clean_session, only the former should be set for MQTT v5. However, in testing with the examples, the only way I can get an MQTT client to connect is setting clean_session to true. Trying to set clean_start to either true or false results in the same error of incompatible MQTT version.

fpagliughi commented 2 years ago

Yeah, this a weird artifact of the underlying Paho C library that was a little tough to work around. There's a single bit in the flags byte of the CONNECT packet that's called Clean Session in v3 and Clean Start in v5. Although the name and usage is slightly different in the two versions of the protocol, they're the same bit in the header. So, as far as connecting go... they're the same thing. But, the C lib gave them separate fields in the connect options struct, and will fail to connect if the wrong one is set!

I'm trying to correct for a mistake in using this when setting or building the options, but since the user can change the version after setting either clean flag, it's a little difficult.

But, basically, if you're targeting v5, just use clean_start(bool) and forget about the other one. If that doesn't work as expected, it's a bug on my part.

BUT... if you want a persistent session in v5 you must set clean_start(false) and then you have to tell the broker how long it is required to keep the session with the SessionExpiryInterval. Like this:

    // Connect with default options, and no clean start.
    // Requests will persist for 1hr (3600sec) if the service disconnects or restarts.
    let conn_opts = mqtt::ConnectOptionsBuilder::new()
        .mqtt_version(5)
        .clean_start(false)
        .properties(mqtt::properties![mqtt::PropertyCode::SessionExpiryInterval => 3600])
        .finalize();

This was missing in previous examples, but I updated the develop branch a few weeks back and will push an update soon.

toddpenland commented 2 years ago

Thank you for the explanation @fpagliughi. The challenge I'm seeing is that the only thing that appears to work for me when using mqtt 5 is setting clean_session(true). Clean_start(true) gives me the following Error: Could not connect to server: [-16] Wrong MQTT version. I've tested it against 2 different brokers just to be sure (Mosquitto and Vernemq). Here is what works:

        let conn_opts = mqtt::ConnectOptionsBuilder::new()
            .keep_alive_interval(Duration::from_secs(30))
            .mqtt_version(mqtt::MQTT_VERSION_5)
            .clean_session(true)
            .will_message(lwt)
            .finalize();

I tried your code above and got the same error (-16). I'd be happy to share more details if it would help get to the bottom of the issue.

fpagliughi commented 1 year ago

Apologies for the delay on this, but I starting to test for a new release, and any small example that you can post that exhibits the error would be helpful.

fpagliughi commented 1 year ago

Actually, are you setting the v5 option when you create the client? Like:

    let create_opts = mqtt::CreateOptionsBuilder::new()
        .mqtt_version(mqtt::MQTT_VERSION_5)                      // <-- Here
        .server_uri(host)
        .client_id("rust_sync_consumer_v5")
        .finalize();

    let cli = mqtt::Client::new(create_opts)?;
fpagliughi commented 1 year ago

Nevermind. This is still a mess trying to do it this way.

The new v0.12 will do it differently. The default create options will get you a "universal" client that can connect using either v3.x or v5. Then you decide when you make the ConnectOptions, but now, you select the version with a specific new_xxx() function, like:

let conn_opts = mqtt::ConnectOptionsBuilder::new_v5()
    .clean_start(false)
    .properties(mqtt::properties![mqtt::PropertyCode::SessionExpiryInterval => 3600])
    .will_message(lwt)
    .finalize();

And the options are guaranteed to be "clean" for the requested version, (i.e. clean start for v5, clean session for v3). You only need to set the value if you want a persistent (non-clean) session.