From ba9319c2dfc2655d529c480306a206a68bca02bc Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:06:37 -0500 Subject: [PATCH] fix(network): migrate to mqtt-client 0.4.0 (IP-literal TLS fix) (#5895) Co-authored-by: Claude Opus 4.8 --- .../meshtastic/core/data/manager/MqttManagerImpl.kt | 5 +++++ core/network/build.gradle.kts | 10 +++++++++- .../core/network/repository/MQTTRepositoryImpl.kt | 6 ++++++ gradle/libs.versions.toml | 10 ++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MqttManagerImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MqttManagerImpl.kt index 72e4587bc..26c51939d 100644 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MqttManagerImpl.kt +++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MqttManagerImpl.kt @@ -43,7 +43,10 @@ import org.meshtastic.mqtt.ConnectionState import org.meshtastic.mqtt.MqttClient import org.meshtastic.mqtt.MqttException import org.meshtastic.mqtt.ProbeResult +import org.meshtastic.mqtt.plus import org.meshtastic.mqtt.probe +import org.meshtastic.mqtt.transport.tcp.TcpTransportFactory +import org.meshtastic.mqtt.transport.ws.WebSocketTransportFactory import org.meshtastic.proto.MqttClientProxyMessage import org.meshtastic.proto.ToRadio import kotlin.uuid.Uuid @@ -136,6 +139,8 @@ class MqttManagerImpl( val endpoint = resolveEndpoint(address, tlsEnabled) val result = MqttClient.probe(endpoint = endpoint) { + // probe() requires a transportFactory in 0.4.0 (errors otherwise); mirror the live client. + transportFactory = TcpTransportFactory() + WebSocketTransportFactory() // Per-connection random suffix: myId identifies the node (and is null → // "unknown" before the node record loads), so two probes can collide on one // client-id and evict each other (SESSION_TAKEN_OVER). See MQTTRepositoryImpl. diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 7ee365597..f195951e1 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -35,7 +35,15 @@ kotlin { implementation(projects.core.ble) implementation(libs.okio) - api(libs.meshtastic.mqtt.client) + // mqtt-client 0.4.0 splits into BOM + core + transport modules. `api` (not `implementation`) + // because :core:data and :desktopApp consume org.meshtastic.mqtt.* types transitively through + // this module. Both transports are registered: transport-tcp (tcp://-/ssl://, default) + + // transport-ws (user-entered ws://-/wss://), composed with `+` at the client config sites. + // No platform() on the KMP commonMain handler; reach the BOM through project.dependencies. + api(project.dependencies.platform(libs.meshtastic.mqtt.client.bom)) + api(libs.meshtastic.mqtt.client.core) + api(libs.meshtastic.mqtt.client.transport.tcp) + api(libs.meshtastic.mqtt.client.transport.ws) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.atomicfu) implementation(libs.ktor.client.core) diff --git a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/repository/MQTTRepositoryImpl.kt b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/repository/MQTTRepositoryImpl.kt index eb2511b40..9f7650781 100644 --- a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/repository/MQTTRepositoryImpl.kt +++ b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/repository/MQTTRepositoryImpl.kt @@ -51,6 +51,9 @@ import org.meshtastic.mqtt.MqttLogLevel import org.meshtastic.mqtt.MqttMessage import org.meshtastic.mqtt.QoS import org.meshtastic.mqtt.packet.Subscription +import org.meshtastic.mqtt.plus +import org.meshtastic.mqtt.transport.tcp.TcpTransportFactory +import org.meshtastic.mqtt.transport.ws.WebSocketTransportFactory import org.meshtastic.proto.ModuleConfig import org.meshtastic.proto.MqttClientProxyMessage import kotlin.concurrent.Volatile @@ -321,6 +324,9 @@ private class DefaultMqttClientSession(private val delegate: MqttClient) : MqttC private fun defaultMqttClientFactory(setup: MqttClientSetup): MqttClientSession = DefaultMqttClientSession( MqttClient(setup.ownerId) { + // mqtt-client 0.4.0 makes transport a required SPI: the client throws at connect if unset. + // Register TCP/TLS (the default) + WebSocket (for user-entered ws://-/wss:// brokers). + transportFactory = TcpTransportFactory() + WebSocketTransportFactory() keepAliveSeconds = MQTT_KEEPALIVE_SECONDS autoReconnect = true username = setup.mqttConfig?.username diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2345408ed..950e01a07 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -95,7 +95,7 @@ osmdroid-android = "6.1.20" spotless = "8.7.0" vico = "3.2.3" kable = "0.43.1" -mqttastic = "0.3.8" +mqttastic = "0.4.0" jmdns = "3.6.3" qrcode-kotlin = "4.5.0" takpacket-sdk = "0.7.0" @@ -264,7 +264,13 @@ markdown-renderer-android = { module = "com.mikepenz:multiplatform-markdown-rend material = { module = "com.google.android.material:material", version = "1.14.0" } kable-core = { module = "com.juul.kable:kable-core", version.ref = "kable" } -meshtastic-mqtt-client = { module = "org.meshtastic:mqtt-client", version.ref = "mqttastic" } +# mqtt-client 0.4.0 split the monolith into a BOM + core + per-transport modules. The BOM pins the +# versions, so core/transport stay versionless. Both transports are wired: transport-tcp (tcp://-/ssl://, +# the default) and transport-ws (for user-entered ws://-/wss:// broker addresses). +meshtastic-mqtt-client-bom = { module = "org.meshtastic:mqtt-client-bom", version.ref = "mqttastic" } +meshtastic-mqtt-client-core = { module = "org.meshtastic:mqtt-client-core" } +meshtastic-mqtt-client-transport-tcp = { module = "org.meshtastic:mqtt-client-transport-tcp" } +meshtastic-mqtt-client-transport-ws = { module = "org.meshtastic:mqtt-client-transport-ws" } jserialcomm = { module = "com.fazecast:jSerialComm", version.ref = "jserialcomm" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" }