@annadata/capacitor-mqtt-quic 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AnnadataCapacitorMqttQuic.podspec +2 -2
- package/Package.swift +59 -0
- package/README.md +57 -17
- package/android/NGTCP2_BUILD_INSTRUCTIONS.md +1 -1
- package/android/app/src/main/assets/capacitor.config.json +1 -1
- package/android/build-wolfssl.sh +8 -2
- package/android/build.gradle +4 -1
- package/android/install/nghttp3-android/arm64-v8a/lib/libnghttp3.a +0 -0
- package/android/install/nghttp3-android/arm64-v8a/lib/libnghttp3.so +0 -0
- package/android/install/nghttp3-android/arm64-v8a/lib/pkgconfig/libnghttp3.pc +4 -4
- package/android/install/nghttp3-android/armeabi-v7a/lib/libnghttp3.a +0 -0
- package/android/install/nghttp3-android/armeabi-v7a/lib/libnghttp3.so +0 -0
- package/android/install/nghttp3-android/armeabi-v7a/lib/pkgconfig/libnghttp3.pc +4 -4
- package/android/install/nghttp3-android/x86_64/lib/libnghttp3.a +0 -0
- package/android/install/nghttp3-android/x86_64/lib/libnghttp3.so +0 -0
- package/android/install/nghttp3-android/x86_64/lib/pkgconfig/libnghttp3.pc +4 -4
- package/android/install/ngtcp2-android/arm64-v8a/lib/libngtcp2.a +0 -0
- package/android/install/ngtcp2-android/arm64-v8a/lib/libngtcp2.so +0 -0
- package/android/install/ngtcp2-android/arm64-v8a/lib/libngtcp2_crypto_wolfssl.a +0 -0
- package/android/install/ngtcp2-android/arm64-v8a/lib/libngtcp2_crypto_wolfssl.so +0 -0
- package/android/install/ngtcp2-android/arm64-v8a/lib/pkgconfig/libngtcp2.pc +4 -4
- package/android/install/ngtcp2-android/arm64-v8a/lib/pkgconfig/libngtcp2_crypto_wolfssl.pc +4 -4
- package/android/install/ngtcp2-android/armeabi-v7a/lib/libngtcp2.a +0 -0
- package/android/install/ngtcp2-android/armeabi-v7a/lib/libngtcp2.so +0 -0
- package/android/install/ngtcp2-android/armeabi-v7a/lib/libngtcp2_crypto_wolfssl.a +0 -0
- package/android/install/ngtcp2-android/armeabi-v7a/lib/libngtcp2_crypto_wolfssl.so +0 -0
- package/android/install/ngtcp2-android/armeabi-v7a/lib/pkgconfig/libngtcp2.pc +4 -4
- package/android/install/ngtcp2-android/armeabi-v7a/lib/pkgconfig/libngtcp2_crypto_wolfssl.pc +4 -4
- package/android/install/ngtcp2-android/x86_64/lib/libngtcp2.a +0 -0
- package/android/install/ngtcp2-android/x86_64/lib/libngtcp2.so +0 -0
- package/android/install/ngtcp2-android/x86_64/lib/libngtcp2_crypto_wolfssl.a +0 -0
- package/android/install/ngtcp2-android/x86_64/lib/libngtcp2_crypto_wolfssl.so +0 -0
- package/android/install/ngtcp2-android/x86_64/lib/pkgconfig/libngtcp2.pc +4 -4
- package/android/install/ngtcp2-android/x86_64/lib/pkgconfig/libngtcp2_crypto_wolfssl.pc +4 -4
- package/android/install/wolfssl-android/arm64-v8a/bin/wolfssl-config +1 -1
- package/android/install/wolfssl-android/arm64-v8a/lib/libwolfssl.a +0 -0
- package/android/install/wolfssl-android/arm64-v8a/lib/libwolfssl.la +1 -1
- package/android/install/wolfssl-android/arm64-v8a/lib/pkgconfig/wolfssl.pc +1 -1
- package/android/install/wolfssl-android/armeabi-v7a/bin/wolfssl-config +1 -1
- package/android/install/wolfssl-android/armeabi-v7a/lib/libwolfssl.a +0 -0
- package/android/install/wolfssl-android/armeabi-v7a/lib/libwolfssl.la +1 -1
- package/android/install/wolfssl-android/armeabi-v7a/lib/pkgconfig/wolfssl.pc +1 -1
- package/android/install/wolfssl-android/x86_64/bin/wolfssl-config +1 -1
- package/android/install/wolfssl-android/x86_64/lib/libwolfssl.a +0 -0
- package/android/install/wolfssl-android/x86_64/lib/libwolfssl.la +1 -1
- package/android/install/wolfssl-android/x86_64/lib/pkgconfig/wolfssl.pc +1 -1
- package/android/src/main/cpp/CMakeLists.txt +19 -5
- package/android/src/main/cpp/ngtcp2_jni.cpp +119 -32
- package/android/src/main/kotlin/ai/annadata/mqttquic/MqttQuicPlugin.kt +60 -5
- package/android/src/main/kotlin/ai/annadata/mqttquic/client/MQTTClient.kt +233 -84
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocol.kt +36 -5
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTTypes.kt +15 -15
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/NGTCP2Client.kt +26 -15
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicClientStub.kt +1 -1
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicTypes.kt +1 -1
- package/android/src/main/kotlin/ai/annadata/mqttquic/transport/QUICStreamAdapter.kt +80 -5
- package/android/src/main/kotlin/ai/annadata/mqttquic/transport/StreamTransport.kt +4 -0
- package/docs/IMPLEMENTATION_SUMMARY.md +1 -1
- package/docs/NGTCP2_IMPLEMENTATION_STATUS.md +1 -1
- package/docs/PRODUCTION_PUBLISH_STEPS.md +9 -3
- package/docs/diff-node_modules-vs-standalone-android-src.patch +1031 -0
- package/ios/{MqttQuicPlugin.podspec → AnnadataCapacitorMqttQuic.podspec} +4 -4
- package/ios/App/App/capacitor.config.json +1 -1
- package/ios/NGTCP2_BUILD_INSTRUCTIONS.md +3 -3
- package/ios/Package.swift +7 -8
- package/ios/Tests/MQTTProtocolTests.swift +1 -1
- package/ios/build-wolfssl.sh +8 -3
- package/ios/libs/libnghttp3.a +0 -0
- package/ios/libs/libngtcp2.a +0 -0
- package/ios/libs/libngtcp2_crypto_wolfssl.a +0 -0
- package/ios/libs/libwolfssl.a +0 -0
- package/ios/libs-simulator/libnghttp3.a +0 -0
- package/ios/libs-simulator/libngtcp2.a +0 -0
- package/ios/libs-simulator/libngtcp2_crypto_wolfssl.a +0 -0
- package/ios/libs-simulator/libwolfssl.a +0 -0
- package/ios/libs-simulator-x86_64/libnghttp3.a +0 -0
- package/ios/libs-simulator-x86_64/libngtcp2.a +0 -0
- package/ios/libs-simulator-x86_64/libngtcp2_crypto_wolfssl.a +0 -0
- package/ios/libs-simulator-x86_64/libwolfssl.a +0 -0
- package/package.json +5 -2
- package/ios/libs/MqttQuicLibs.xcframework/Info.plist +0 -44
- package/ios/libs/MqttQuicLibs.xcframework/ios-arm64/libmqttquic_native_device.a +0 -0
- package/ios/libs/MqttQuicLibs.xcframework/ios-arm64_x86_64-simulator/libmqttquic_native_simulator.a +0 -0
|
@@ -36,7 +36,7 @@ class QuicClientStub(private val initialReadData: List<Byte> = emptyList()) : Qu
|
|
|
36
36
|
private var buffer: MockStreamBuffer? = null
|
|
37
37
|
private var streamId = 0L
|
|
38
38
|
|
|
39
|
-
override suspend fun connect(host: String, port: Int) {
|
|
39
|
+
override suspend fun connect(host: String, port: Int, connectAddress: String?) {
|
|
40
40
|
buffer = MockStreamBuffer(initialReadData.toByteArray())
|
|
41
41
|
streamId = 0L
|
|
42
42
|
}
|
|
@@ -15,7 +15,7 @@ interface QuicStream {
|
|
|
15
15
|
* QUIC client: connect, TLS handshake, open one bidirectional stream.
|
|
16
16
|
*/
|
|
17
17
|
interface QuicClient {
|
|
18
|
-
suspend fun connect(host: String, port: Int)
|
|
18
|
+
suspend fun connect(host: String, port: Int, connectAddress: String? = null)
|
|
19
19
|
suspend fun openStream(): QuicStream
|
|
20
20
|
suspend fun close()
|
|
21
21
|
}
|
|
@@ -1,21 +1,96 @@
|
|
|
1
1
|
package ai.annadata.mqttquic.transport
|
|
2
2
|
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import ai.annadata.mqttquic.mqtt.MQTTProtocol
|
|
3
5
|
import ai.annadata.mqttquic.quic.QuicStream
|
|
6
|
+
import kotlinx.coroutines.delay
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
|
-
* MQTTStreamReader over QUIC stream.
|
|
9
|
+
* MQTTStreamReader over QUIC stream. Buffers excess bytes so readexactly(n)
|
|
10
|
+
* and read(maxBytes) get exactly the requested amount; native may return
|
|
11
|
+
* a full CONNACK (e.g. 18 bytes) in one read, so we must not lose the remainder.
|
|
12
|
+
*
|
|
13
|
+
* Efficient CONNACK/packet read: call [drain] to read until the stream has no more
|
|
14
|
+
* data, then [tryConsumeNextPacket] to take the first complete MQTT packet from
|
|
15
|
+
* the buffer. Repeat drain + tryConsumeNextPacket (with short delay) until you
|
|
16
|
+
* get a packet or timeout.
|
|
7
17
|
*/
|
|
8
18
|
class QUICStreamReader(private val stream: QuicStream) : MQTTStreamReader {
|
|
9
19
|
|
|
10
|
-
|
|
20
|
+
private val buffer = mutableListOf<Byte>()
|
|
21
|
+
|
|
22
|
+
override suspend fun available(): Int = buffer.size
|
|
23
|
+
|
|
24
|
+
/** Read from stream until no more data is available (drained). Call before tryConsumeNextPacket. */
|
|
25
|
+
suspend fun drain() {
|
|
26
|
+
while (true) {
|
|
27
|
+
val chunk = stream.read(8192)
|
|
28
|
+
if (chunk.isEmpty()) break
|
|
29
|
+
Log.i("MQTTClient", "QUICStreamReader: drain got ${chunk.size} bytes bufferTotal=${buffer.size + chunk.size}")
|
|
30
|
+
buffer.addAll(chunk.toList())
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Consume the first n bytes from buffer and return them. Caller must ensure buffer.size >= n. */
|
|
35
|
+
fun consume(n: Int): ByteArray {
|
|
36
|
+
if (buffer.size < n) throw IllegalArgumentException("buffer has ${buffer.size} < $n")
|
|
37
|
+
val out = buffer.take(n).toByteArray()
|
|
38
|
+
repeat(n) { buffer.removeAt(0) }
|
|
39
|
+
return out
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* If buffer contains at least one complete MQTT packet (fixed header + payload), consume and return it; else return null.
|
|
44
|
+
* Call after [drain]; if null, delay and drain again (or timeout).
|
|
45
|
+
*/
|
|
46
|
+
fun tryConsumeNextPacket(): ByteArray? {
|
|
47
|
+
val buf = buffer.toByteArray()
|
|
48
|
+
val totalLen = MQTTProtocol.getNextPacketLength(buf)
|
|
49
|
+
if (totalLen == null) {
|
|
50
|
+
if (buf.isNotEmpty()) {
|
|
51
|
+
Log.w("MQTTClient", "QUICStreamReader: getNextPacketLength returned null bufferSize=${buf.size} firstByte=0x${Integer.toHexString(buf[0].toInt() and 0xFF)}")
|
|
52
|
+
}
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
if (buffer.size < totalLen) {
|
|
56
|
+
Log.i("MQTTClient", "QUICStreamReader: buffer.size=${buffer.size} < totalLen=$totalLen waiting for more")
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
val packet = consume(totalLen)
|
|
60
|
+
Log.i("MQTTClient", "QUICStreamReader: tryConsumeNextPacket consumed $totalLen bytes type=0x${Integer.toHexString(packet[0].toInt() and 0xFF)}")
|
|
61
|
+
return packet
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override suspend fun read(maxBytes: Int): ByteArray {
|
|
65
|
+
while (buffer.size < maxBytes) {
|
|
66
|
+
val chunk = stream.read(maxBytes - buffer.size)
|
|
67
|
+
if (chunk.isEmpty()) break
|
|
68
|
+
Log.i("MQTTClient", "QUICStreamReader: got chunk=${chunk.size} bufferSize=${buffer.size + chunk.size}")
|
|
69
|
+
buffer.addAll(chunk.toList())
|
|
70
|
+
}
|
|
71
|
+
val n = minOf(maxBytes, buffer.size)
|
|
72
|
+
if (n == 0) return ByteArray(0)
|
|
73
|
+
val result = buffer.subList(0, n).toByteArray()
|
|
74
|
+
repeat(n) { buffer.removeAt(0) }
|
|
75
|
+
Log.i("MQTTClient", "QUICStreamReader: returning $n bytes bufferRemain=${buffer.size}")
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
11
78
|
|
|
12
79
|
override suspend fun readexactly(n: Int): ByteArray {
|
|
80
|
+
Log.i("MQTTClient", "QUICStreamReader: readexactly($n) bufferHas=${buffer.size}")
|
|
13
81
|
val acc = mutableListOf<Byte>()
|
|
14
82
|
while (acc.size < n) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
83
|
+
drain()
|
|
84
|
+
val fromBuffer = minOf(n - acc.size, buffer.size)
|
|
85
|
+
if (fromBuffer > 0) {
|
|
86
|
+
acc.addAll(buffer.subList(0, fromBuffer).toList())
|
|
87
|
+
repeat(fromBuffer) { buffer.removeAt(0) }
|
|
88
|
+
} else {
|
|
89
|
+
// No data yet (e.g. message loop waiting for SUBACK/PUBLISH). Wait and retry instead of throwing.
|
|
90
|
+
delay(20L)
|
|
91
|
+
}
|
|
18
92
|
}
|
|
93
|
+
Log.i("MQTTClient", "QUICStreamReader: readexactly($n) done")
|
|
19
94
|
return acc.toByteArray()
|
|
20
95
|
}
|
|
21
96
|
}
|
|
@@ -7,6 +7,8 @@ import kotlinx.coroutines.delay
|
|
|
7
7
|
* Phase 2 implements over QUIC stream.
|
|
8
8
|
*/
|
|
9
9
|
interface MQTTStreamReader {
|
|
10
|
+
/** Number of bytes currently buffered (without reading from stream). Used to skip unwanted packets only when safe. */
|
|
11
|
+
suspend fun available(): Int
|
|
10
12
|
suspend fun read(maxBytes: Int): ByteArray
|
|
11
13
|
suspend fun readexactly(n: Int): ByteArray
|
|
12
14
|
}
|
|
@@ -50,6 +52,8 @@ class MockStreamBuffer(initialReadData: ByteArray = ByteArray(0)) {
|
|
|
50
52
|
*/
|
|
51
53
|
class MockStreamReader(private val buffer: MockStreamBuffer) : MQTTStreamReader {
|
|
52
54
|
|
|
55
|
+
override suspend fun available(): Int = buffer.readBuffer.size
|
|
56
|
+
|
|
53
57
|
override suspend fun read(maxBytes: Int): ByteArray {
|
|
54
58
|
if (buffer.isClosed && buffer.readBuffer.isEmpty()) return ByteArray(0)
|
|
55
59
|
val n = minOf(maxBytes, buffer.readBuffer.size)
|
|
@@ -182,7 +182,7 @@ Implement `MqttQuicService` web fallback using `mqtt.js` or similar library.
|
|
|
182
182
|
```
|
|
183
183
|
capacitor-mqtt-quic/
|
|
184
184
|
├── ios/
|
|
185
|
-
│ ├──
|
|
185
|
+
│ ├── AnnadataCapacitorMqttQuic.podspec
|
|
186
186
|
│ └── Sources/MqttQuicPlugin/
|
|
187
187
|
│ ├── MQTT/ # Phase 1
|
|
188
188
|
│ ├── Transport/ # Phase 1, 2
|
|
@@ -129,7 +129,7 @@ The ngtcp2 integration replaces the stub QUIC implementations (`QuicClientStub`)
|
|
|
129
129
|
- Implement TLS 1.3 handshake
|
|
130
130
|
- Implement UDP packet handlers
|
|
131
131
|
|
|
132
|
-
4. **Update `
|
|
132
|
+
4. **Update `AnnadataCapacitorMqttQuic.podspec`:**
|
|
133
133
|
- Add ngtcp2 library linking
|
|
134
134
|
- Add header search paths
|
|
135
135
|
|
|
@@ -9,7 +9,7 @@ This checklist makes the plugin **production-grade**: pack everything needed (in
|
|
|
9
9
|
## Prerequisites
|
|
10
10
|
|
|
11
11
|
- Node.js 18+
|
|
12
|
-
- **iOS:** macOS, Xcode
|
|
12
|
+
- **iOS:** macOS, Xcode 15+
|
|
13
13
|
- **Android:** Android Studio, NDK r25+ (e.g. via `$ANDROID_HOME/ndk/<version>`)
|
|
14
14
|
- npm account with access to `@annadata` scope
|
|
15
15
|
|
|
@@ -32,9 +32,15 @@ This produces:
|
|
|
32
32
|
|
|
33
33
|
The podspec’s `vendored_libraries` point at `ios/libs/`; these **must** be present when you pack/publish.
|
|
34
34
|
|
|
35
|
-
### Android (
|
|
35
|
+
### Android (required for zero-config / “complete” package)
|
|
36
36
|
|
|
37
|
-
Android uses **WolfSSL** (same TLS backend as iOS).
|
|
37
|
+
Android uses **WolfSSL** (same TLS backend as iOS). To ship a **complete** plugin so clients don’t run any native build, build WolfSSL + nghttp3 + ngtcp2 for all ABIs:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm run build:android-prebuilts
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or manually:
|
|
38
44
|
|
|
39
45
|
```bash
|
|
40
46
|
./build-native.sh --android-only --abi arm64-v8a
|