@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.
Files changed (83) hide show
  1. package/AnnadataCapacitorMqttQuic.podspec +2 -2
  2. package/Package.swift +59 -0
  3. package/README.md +57 -17
  4. package/android/NGTCP2_BUILD_INSTRUCTIONS.md +1 -1
  5. package/android/app/src/main/assets/capacitor.config.json +1 -1
  6. package/android/build-wolfssl.sh +8 -2
  7. package/android/build.gradle +4 -1
  8. package/android/install/nghttp3-android/arm64-v8a/lib/libnghttp3.a +0 -0
  9. package/android/install/nghttp3-android/arm64-v8a/lib/libnghttp3.so +0 -0
  10. package/android/install/nghttp3-android/arm64-v8a/lib/pkgconfig/libnghttp3.pc +4 -4
  11. package/android/install/nghttp3-android/armeabi-v7a/lib/libnghttp3.a +0 -0
  12. package/android/install/nghttp3-android/armeabi-v7a/lib/libnghttp3.so +0 -0
  13. package/android/install/nghttp3-android/armeabi-v7a/lib/pkgconfig/libnghttp3.pc +4 -4
  14. package/android/install/nghttp3-android/x86_64/lib/libnghttp3.a +0 -0
  15. package/android/install/nghttp3-android/x86_64/lib/libnghttp3.so +0 -0
  16. package/android/install/nghttp3-android/x86_64/lib/pkgconfig/libnghttp3.pc +4 -4
  17. package/android/install/ngtcp2-android/arm64-v8a/lib/libngtcp2.a +0 -0
  18. package/android/install/ngtcp2-android/arm64-v8a/lib/libngtcp2.so +0 -0
  19. package/android/install/ngtcp2-android/arm64-v8a/lib/libngtcp2_crypto_wolfssl.a +0 -0
  20. package/android/install/ngtcp2-android/arm64-v8a/lib/libngtcp2_crypto_wolfssl.so +0 -0
  21. package/android/install/ngtcp2-android/arm64-v8a/lib/pkgconfig/libngtcp2.pc +4 -4
  22. package/android/install/ngtcp2-android/arm64-v8a/lib/pkgconfig/libngtcp2_crypto_wolfssl.pc +4 -4
  23. package/android/install/ngtcp2-android/armeabi-v7a/lib/libngtcp2.a +0 -0
  24. package/android/install/ngtcp2-android/armeabi-v7a/lib/libngtcp2.so +0 -0
  25. package/android/install/ngtcp2-android/armeabi-v7a/lib/libngtcp2_crypto_wolfssl.a +0 -0
  26. package/android/install/ngtcp2-android/armeabi-v7a/lib/libngtcp2_crypto_wolfssl.so +0 -0
  27. package/android/install/ngtcp2-android/armeabi-v7a/lib/pkgconfig/libngtcp2.pc +4 -4
  28. package/android/install/ngtcp2-android/armeabi-v7a/lib/pkgconfig/libngtcp2_crypto_wolfssl.pc +4 -4
  29. package/android/install/ngtcp2-android/x86_64/lib/libngtcp2.a +0 -0
  30. package/android/install/ngtcp2-android/x86_64/lib/libngtcp2.so +0 -0
  31. package/android/install/ngtcp2-android/x86_64/lib/libngtcp2_crypto_wolfssl.a +0 -0
  32. package/android/install/ngtcp2-android/x86_64/lib/libngtcp2_crypto_wolfssl.so +0 -0
  33. package/android/install/ngtcp2-android/x86_64/lib/pkgconfig/libngtcp2.pc +4 -4
  34. package/android/install/ngtcp2-android/x86_64/lib/pkgconfig/libngtcp2_crypto_wolfssl.pc +4 -4
  35. package/android/install/wolfssl-android/arm64-v8a/bin/wolfssl-config +1 -1
  36. package/android/install/wolfssl-android/arm64-v8a/lib/libwolfssl.a +0 -0
  37. package/android/install/wolfssl-android/arm64-v8a/lib/libwolfssl.la +1 -1
  38. package/android/install/wolfssl-android/arm64-v8a/lib/pkgconfig/wolfssl.pc +1 -1
  39. package/android/install/wolfssl-android/armeabi-v7a/bin/wolfssl-config +1 -1
  40. package/android/install/wolfssl-android/armeabi-v7a/lib/libwolfssl.a +0 -0
  41. package/android/install/wolfssl-android/armeabi-v7a/lib/libwolfssl.la +1 -1
  42. package/android/install/wolfssl-android/armeabi-v7a/lib/pkgconfig/wolfssl.pc +1 -1
  43. package/android/install/wolfssl-android/x86_64/bin/wolfssl-config +1 -1
  44. package/android/install/wolfssl-android/x86_64/lib/libwolfssl.a +0 -0
  45. package/android/install/wolfssl-android/x86_64/lib/libwolfssl.la +1 -1
  46. package/android/install/wolfssl-android/x86_64/lib/pkgconfig/wolfssl.pc +1 -1
  47. package/android/src/main/cpp/CMakeLists.txt +19 -5
  48. package/android/src/main/cpp/ngtcp2_jni.cpp +119 -32
  49. package/android/src/main/kotlin/ai/annadata/mqttquic/MqttQuicPlugin.kt +60 -5
  50. package/android/src/main/kotlin/ai/annadata/mqttquic/client/MQTTClient.kt +233 -84
  51. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocol.kt +36 -5
  52. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTTypes.kt +15 -15
  53. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/NGTCP2Client.kt +26 -15
  54. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicClientStub.kt +1 -1
  55. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicTypes.kt +1 -1
  56. package/android/src/main/kotlin/ai/annadata/mqttquic/transport/QUICStreamAdapter.kt +80 -5
  57. package/android/src/main/kotlin/ai/annadata/mqttquic/transport/StreamTransport.kt +4 -0
  58. package/docs/IMPLEMENTATION_SUMMARY.md +1 -1
  59. package/docs/NGTCP2_IMPLEMENTATION_STATUS.md +1 -1
  60. package/docs/PRODUCTION_PUBLISH_STEPS.md +9 -3
  61. package/docs/diff-node_modules-vs-standalone-android-src.patch +1031 -0
  62. package/ios/{MqttQuicPlugin.podspec → AnnadataCapacitorMqttQuic.podspec} +4 -4
  63. package/ios/App/App/capacitor.config.json +1 -1
  64. package/ios/NGTCP2_BUILD_INSTRUCTIONS.md +3 -3
  65. package/ios/Package.swift +7 -8
  66. package/ios/Tests/MQTTProtocolTests.swift +1 -1
  67. package/ios/build-wolfssl.sh +8 -3
  68. package/ios/libs/libnghttp3.a +0 -0
  69. package/ios/libs/libngtcp2.a +0 -0
  70. package/ios/libs/libngtcp2_crypto_wolfssl.a +0 -0
  71. package/ios/libs/libwolfssl.a +0 -0
  72. package/ios/libs-simulator/libnghttp3.a +0 -0
  73. package/ios/libs-simulator/libngtcp2.a +0 -0
  74. package/ios/libs-simulator/libngtcp2_crypto_wolfssl.a +0 -0
  75. package/ios/libs-simulator/libwolfssl.a +0 -0
  76. package/ios/libs-simulator-x86_64/libnghttp3.a +0 -0
  77. package/ios/libs-simulator-x86_64/libngtcp2.a +0 -0
  78. package/ios/libs-simulator-x86_64/libngtcp2_crypto_wolfssl.a +0 -0
  79. package/ios/libs-simulator-x86_64/libwolfssl.a +0 -0
  80. package/package.json +5 -2
  81. package/ios/libs/MqttQuicLibs.xcframework/Info.plist +0 -44
  82. package/ios/libs/MqttQuicLibs.xcframework/ios-arm64/libmqttquic_native_device.a +0 -0
  83. 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. Mirrors NGTCP2StreamReader.
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
- override suspend fun read(maxBytes: Int): ByteArray = stream.read(maxBytes)
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
- val chunk = stream.read(n - acc.size)
16
- if (chunk.isEmpty()) throw IllegalArgumentException("readexactly")
17
- acc.addAll(chunk.toList())
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
- │ ├── MqttQuicPlugin.podspec
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 `MqttQuicPlugin.podspec`:**
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 14+
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 (optional but recommended for zero-config)
35
+ ### Android (required for zero-config / “complete” package)
36
36
 
37
- Android uses **WolfSSL** (same TLS backend as iOS). Build WolfSSL + nghttp3 + ngtcp2 for all ABIs so consumers don’t need to run the one-time setup:
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