Skip to content

Commit 49b7a7b

Browse files
jamesarichclaude
andauthored
feat(transport): add TLS trust hook to WebSocketTransportFactory (#108)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent b6963d8 commit 49b7a7b

27 files changed

Lines changed: 2528 additions & 43 deletions

File tree

AGENTS.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,20 @@ check; see `core/build.gradle.kts` `verifyModuleBoundary`). Everything in `:core
4343
integration tests (testImplementation project(":transport-tcp"))
4444
:transport-tcp commonMain (TcpTransport) + jvm/android/native PlatformTls expect/actual.
4545
Targets: jvm, android, apple (ios/macos), linux, mingw — NO wasmJs.
46-
:transport-ws commonMain (WebSocketTransport) + per-platform Ktor engine deps.
47-
Targets: all, incl. wasmJs.
46+
:transport-ws commonMain (WebSocketTransport) + cioMain (CIO engine + TLS trust hook)
47+
+ per-platform Ktor engine deps. Targets: all, incl. wasmJs.
4848
:bom (mqtt-client-bom) java-platform BOM pinning every artifact to one version.
4949
build-logic/convention mqtt.kmp.library + mqtt.publishing convention plugins (shared KMP target
5050
set + per-module publishing coordinates derived from the module name).
5151
```
5252

5353
Each library module applies `applyDefaultHierarchyTemplate()` (via `mqtt.kmp.library`), so
5454
`nativeMain`/`appleMain`/`linuxMain`/`mingwMain` are auto-created. macosArm64 only (macosX64
55-
deprecated in Kotlin 2.3.20). There is **no** custom `nonWebMain` source set any more: `:transport-tcp`
56-
simply omits the wasmJs target, so its `commonMain` is effectively the old "non-web" set.
55+
deprecated in Kotlin 2.3.20). The one custom intermediate source set is `:transport-ws`'s `cioMain`
56+
(jvm + android + apple + linux), which holds the single CIO `HttpClient` builder and the TLS trust
57+
plumbing (`applyWsTls` and the `configurePlatformTrust` expect/actuals). The old `nonWebMain` set is
58+
gone: `:transport-tcp` simply omits the wasmJs target, so its `commonMain` is effectively the old
59+
"non-web" set.
5760

5861
### Packet codec pipeline
5962

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- `WebSocketTransportFactory` now accepts an optional TLS customisation lambda, the WebSocket
13+
counterpart to the `TcpTransportFactory` hook added in 0.6.0. Both take the same
14+
`TLSConfigBuilder` receiver, so a single trust manager can serve `ssl://` and `wss://` brokers
15+
behind a private or self-signed CA — scoped to the MQTT connection, with no app-wide
16+
`network_security_config.xml` anchor. The lambda runs on JVM, Android, Apple, and Linux; ignored
17+
on Windows (WinHttp exposes no TLS-configuration surface) and in the browser. The private-CA
18+
trust manager itself is available only on JVM and Android, where `TLSConfigBuilder` exposes
19+
`trustManager` — on Apple and Linux the lambda still runs but has no equivalent property to set.
20+
The existing no-arg constructor is unchanged (#107).
21+
22+
### Changed
23+
24+
- `:transport-ws` now selects its Ktor engine explicitly per platform (CIO on JVM/Android/Apple/
25+
Linux, WinHttp on Windows, Js on wasmJs) instead of relying on auto-detection. These are the
26+
engines the library itself ships for each target; auto-detection resolves via `ServiceLoader`
27+
over the *consuming app's* classpath, not the library's, so an app that also pulls in another
28+
Ktor engine (e.g. `ktor-client-okhttp`, common on Android) may previously have had its WebSocket
29+
MQTT connection served by that engine instead of CIO, with different proxy, DNS, and socket
30+
defaults. Such an app will now always get CIO for this transport.
31+
1032
## [0.6.1] - 2026-07-26
1133

1234
**The published library artifacts are unchanged from 0.6.0.** `mqtt-client-core`,

README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,11 @@ Log levels from most to least verbose: `TRACE` → `DEBUG` → `INFO` → `WARN`
334334

335335
### Custom TLS trust
336336

337-
By default the TCP transport validates the broker certificate against the platform CA store. To
338-
reach a broker behind a private or self-signed CA, pass a TLS customisation lambda to
339-
`TcpTransportFactory`. It runs against ktor's `TLSConfigBuilder`:
337+
By default both transports validate the broker certificate against the platform CA store. To
338+
reach a broker behind a private or self-signed CA, pass a TLS customisation lambda to the
339+
transport factory you use — `TcpTransportFactory`, `WebSocketTransportFactory`, or both, since a
340+
factory without the lambda keeps validating against the platform store alone. It runs against
341+
ktor's `TLSConfigBuilder`:
340342

341343
```kotlin
342344
import org.meshtastic.mqtt.transport.tcp.TcpTransportFactory
@@ -377,18 +379,21 @@ This scopes the extra trust to the MQTT connection alone. It replaces the app-wi
377379
adding `<certificates src="user"/>` to `network_security_config.xml`, which would affect every
378380
HTTPS connection the app makes.
379381

380-
The hook composes with transport selection as usual:
382+
The hook composes with transport selection as usual, and both transports take the same lambda type,
383+
so one trust manager can serve both:
381384

382385
```kotlin
383386
transportFactory = TcpTransportFactory { trustManager = myPrivateCaTrustManager } +
384-
WebSocketTransportFactory()
387+
WebSocketTransportFactory { trustManager = myPrivateCaTrustManager }
385388
```
386389

387-
`TLSConfigBuilder` comes from `io.ktor:ktor-network-tls`, exposed transitively by
388-
`mqtt-client-transport-tcp` — no extra dependency needed. The WebSocket transport has no equivalent
389-
hook yet. `trustManager` specifically is available on the JVM and Android actuals of
390-
`TLSConfigBuilder`; on Apple, Linux, and Windows targets the hook still runs, but `TLSConfigBuilder`
391-
exposes a different set of properties there.
390+
`TLSConfigBuilder` comes from `io.ktor:ktor-network-tls`, exposed transitively by both
391+
`mqtt-client-transport-tcp` and `mqtt-client-transport-ws` — no extra dependency needed.
392+
`trustManager` specifically is available on the JVM and Android actuals of `TLSConfigBuilder`; on
393+
Apple and Linux the hook still runs, but `TLSConfigBuilder` exposes a different set of properties
394+
there. The WebSocket hook is additionally ignored on Windows (the WinHttp engine has no
395+
TLS-configuration surface) and in the browser (which cannot influence trust) — see
396+
`transport-ws/Module.md` for the per-target table.
392397

393398
## Android / KMP Integration
394399

0 commit comments

Comments
 (0)