@annadata/capacitor-mqtt-quic 0.1.0

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 (64) hide show
  1. package/README.md +399 -0
  2. package/android/NGTCP2_BUILD_INSTRUCTIONS.md +319 -0
  3. package/android/build-nghttp3.sh +182 -0
  4. package/android/build-ngtcp2.sh +289 -0
  5. package/android/build-openssl.sh +302 -0
  6. package/android/build.gradle +75 -0
  7. package/android/gradle.properties +3 -0
  8. package/android/proguard-rules.pro +2 -0
  9. package/android/settings.gradle +1 -0
  10. package/android/src/main/AndroidManifest.xml +1 -0
  11. package/android/src/main/assets/mqttquic_ca.pem +5 -0
  12. package/android/src/main/cpp/CMakeLists.txt +157 -0
  13. package/android/src/main/cpp/ngtcp2_jni.cpp +928 -0
  14. package/android/src/main/kotlin/ai/annadata/mqttquic/MqttQuicPlugin.kt +232 -0
  15. package/android/src/main/kotlin/ai/annadata/mqttquic/client/MQTTClient.kt +339 -0
  16. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Properties.kt +250 -0
  17. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Protocol.kt +281 -0
  18. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5ReasonCodes.kt +109 -0
  19. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocol.kt +249 -0
  20. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTTypes.kt +47 -0
  21. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/NGTCP2Client.kt +184 -0
  22. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicClientStub.kt +54 -0
  23. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicTypes.kt +21 -0
  24. package/android/src/main/kotlin/ai/annadata/mqttquic/transport/QUICStreamAdapter.kt +37 -0
  25. package/android/src/main/kotlin/ai/annadata/mqttquic/transport/StreamTransport.kt +91 -0
  26. package/android/src/test/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocolTest.kt +92 -0
  27. package/dist/esm/definitions.d.ts +66 -0
  28. package/dist/esm/definitions.d.ts.map +1 -0
  29. package/dist/esm/definitions.js +2 -0
  30. package/dist/esm/definitions.js.map +1 -0
  31. package/dist/esm/index.d.ts +5 -0
  32. package/dist/esm/index.d.ts.map +1 -0
  33. package/dist/esm/index.js +7 -0
  34. package/dist/esm/index.js.map +1 -0
  35. package/dist/esm/web.d.ts +28 -0
  36. package/dist/esm/web.d.ts.map +1 -0
  37. package/dist/esm/web.js +183 -0
  38. package/dist/esm/web.js.map +1 -0
  39. package/dist/plugin.cjs.js +217 -0
  40. package/dist/plugin.cjs.js.map +1 -0
  41. package/dist/plugin.js +215 -0
  42. package/dist/plugin.js.map +1 -0
  43. package/ios/MqttQuicPlugin.podspec +34 -0
  44. package/ios/NGTCP2_BUILD_INSTRUCTIONS.md +302 -0
  45. package/ios/Sources/MqttQuicPlugin/Client/MQTTClient.swift +343 -0
  46. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Properties.swift +280 -0
  47. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Protocol.swift +333 -0
  48. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5ReasonCodes.swift +113 -0
  49. package/ios/Sources/MqttQuicPlugin/MQTT/MQTTProtocol.swift +322 -0
  50. package/ios/Sources/MqttQuicPlugin/MQTT/MQTTTypes.swift +54 -0
  51. package/ios/Sources/MqttQuicPlugin/MqttQuicPlugin.swift +229 -0
  52. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.h +29 -0
  53. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.mm +865 -0
  54. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Client.swift +262 -0
  55. package/ios/Sources/MqttQuicPlugin/QUIC/QuicClientStub.swift +66 -0
  56. package/ios/Sources/MqttQuicPlugin/QUIC/QuicTypes.swift +23 -0
  57. package/ios/Sources/MqttQuicPlugin/Resources/mqttquic_ca.pem +5 -0
  58. package/ios/Sources/MqttQuicPlugin/Transport/QUICStreamAdapter.swift +50 -0
  59. package/ios/Sources/MqttQuicPlugin/Transport/StreamTransport.swift +105 -0
  60. package/ios/Tests/MQTTProtocolTests.swift +82 -0
  61. package/ios/build-nghttp3.sh +173 -0
  62. package/ios/build-ngtcp2.sh +278 -0
  63. package/ios/build-openssl.sh +405 -0
  64. package/package.json +63 -0
@@ -0,0 +1,928 @@
1
+ //
2
+ // ngtcp2_jni.cpp
3
+ // MqttQuicPlugin
4
+ //
5
+ // JNI wrapper for ngtcp2 QUIC client implementation.
6
+ //
7
+
8
+ #include <jni.h>
9
+ #include <android/log.h>
10
+
11
+ #include <ngtcp2/ngtcp2.h>
12
+ #include <ngtcp2/ngtcp2_crypto.h>
13
+ #include <ngtcp2/ngtcp2_crypto_quictls.h>
14
+
15
+ #include <openssl/ssl.h>
16
+ #include <openssl/rand.h>
17
+ #include <openssl/err.h>
18
+
19
+ #include <arpa/inet.h>
20
+ #include <fcntl.h>
21
+ #include <netdb.h>
22
+ #include <poll.h>
23
+ #include <sys/socket.h>
24
+ #include <unistd.h>
25
+
26
+ #include <algorithm>
27
+ #include <atomic>
28
+ #include <chrono>
29
+ #include <cstdarg>
30
+ #include <cstdlib>
31
+ #include <cstdio>
32
+ #include <condition_variable>
33
+ #include <deque>
34
+ #include <map>
35
+ #include <mutex>
36
+ #include <string>
37
+ #include <thread>
38
+ #include <vector>
39
+
40
+ #define LOG_TAG "NGTCP2JNI"
41
+ #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
42
+ #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
43
+
44
+ namespace {
45
+
46
+ static uint64_t now_ts() {
47
+ struct timespec tp;
48
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
49
+ return 0;
50
+ }
51
+ return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec;
52
+ }
53
+
54
+ static void log_printf(void *user_data, const char *fmt, ...) {
55
+ (void)user_data;
56
+ va_list ap;
57
+ va_start(ap, fmt);
58
+ vfprintf(stderr, fmt, ap);
59
+ va_end(ap);
60
+ fprintf(stderr, "\n");
61
+ }
62
+
63
+ struct StreamState {
64
+ std::deque<uint8_t> recv_buf;
65
+ bool fin_received = false;
66
+ bool closed = false;
67
+ };
68
+
69
+ struct OutgoingChunk {
70
+ std::vector<uint8_t> data;
71
+ size_t offset = 0;
72
+ bool fin = false;
73
+ };
74
+
75
+ class QuicClient {
76
+ public:
77
+ QuicClient(std::string host, uint16_t port)
78
+ : host_(std::move(host)),
79
+ port_(port),
80
+ fd_(-1),
81
+ ssl_ctx_(nullptr),
82
+ ssl_(nullptr),
83
+ conn_(nullptr),
84
+ running_(false),
85
+ connected_(false),
86
+ close_requested_(false) {
87
+ ngtcp2_ccerr_default(&last_error_);
88
+ conn_ref_.get_conn = get_conn;
89
+ conn_ref_.user_data = this;
90
+ wakeup_fds_[0] = -1;
91
+ wakeup_fds_[1] = -1;
92
+ }
93
+
94
+ ~QuicClient() { close(); }
95
+
96
+ int connect(const std::string &alpn) {
97
+ {
98
+ std::lock_guard<std::mutex> lock(state_mutex_);
99
+ if (connected_) {
100
+ return 0;
101
+ }
102
+ }
103
+ clearError();
104
+ if (init_socket() != 0) {
105
+ return -1;
106
+ }
107
+ if (init_tls(alpn) != 0) {
108
+ return -1;
109
+ }
110
+ if (init_quic() != 0) {
111
+ return -1;
112
+ }
113
+ if (init_wakeup_pipe() != 0) {
114
+ return -1;
115
+ }
116
+
117
+ running_ = true;
118
+ worker_ = std::thread([this]() { run_loop(); });
119
+ signal_wakeup();
120
+
121
+ std::unique_lock<std::mutex> wait_lock(state_mutex_);
122
+ if (!cv_state_.wait_for(wait_lock, std::chrono::seconds(15), [this]() {
123
+ return connected_ || !running_;
124
+ })) {
125
+ setError("QUIC handshake timed out");
126
+ return -1;
127
+ }
128
+ if (!connected_) {
129
+ if (last_error_str_.empty()) {
130
+ setError("QUIC handshake failed");
131
+ }
132
+ return -1;
133
+ }
134
+ return 0;
135
+ }
136
+
137
+ int64_t open_stream() {
138
+ if (!conn_) {
139
+ setError("QUIC connection not initialized");
140
+ return -1;
141
+ }
142
+ int64_t stream_id = -1;
143
+ int rv = ngtcp2_conn_open_bidi_stream(conn_, &stream_id, nullptr);
144
+ if (rv != 0) {
145
+ setError(ngtcp2_strerror(rv));
146
+ return -1;
147
+ }
148
+ {
149
+ std::lock_guard<std::mutex> lock(stream_mutex_);
150
+ streams_.emplace(stream_id, StreamState{});
151
+ }
152
+ signal_wakeup();
153
+ return stream_id;
154
+ }
155
+
156
+ int write_stream(int64_t stream_id, const uint8_t *data, size_t datalen,
157
+ bool fin) {
158
+ if (!conn_) {
159
+ setError("QUIC connection not initialized");
160
+ return -1;
161
+ }
162
+ OutgoingChunk chunk;
163
+ chunk.data.assign(data, data + datalen);
164
+ chunk.offset = 0;
165
+ chunk.fin = fin;
166
+ {
167
+ std::lock_guard<std::mutex> lock(out_mutex_);
168
+ outgoing_[stream_id].push_back(std::move(chunk));
169
+ }
170
+ signal_wakeup();
171
+ return 0;
172
+ }
173
+
174
+ ssize_t read_stream(int64_t stream_id, uint8_t *buffer, size_t maxlen) {
175
+ std::lock_guard<std::mutex> lock(stream_mutex_);
176
+ auto it = streams_.find(stream_id);
177
+ if (it == streams_.end()) {
178
+ return 0;
179
+ }
180
+ StreamState &state = it->second;
181
+ size_t n = std::min(maxlen, state.recv_buf.size());
182
+ for (size_t i = 0; i < n; ++i) {
183
+ buffer[i] = state.recv_buf.front();
184
+ state.recv_buf.pop_front();
185
+ }
186
+ return (ssize_t)n;
187
+ }
188
+
189
+ int close_stream(int64_t stream_id) {
190
+ if (!conn_) {
191
+ return 0;
192
+ }
193
+ int rv = ngtcp2_conn_shutdown_stream_write(conn_, stream_id, 0);
194
+ if (rv != 0) {
195
+ setError(ngtcp2_strerror(rv));
196
+ return -1;
197
+ }
198
+ signal_wakeup();
199
+ return 0;
200
+ }
201
+
202
+ int close() {
203
+ {
204
+ std::lock_guard<std::mutex> lock(state_mutex_);
205
+ if (!running_) {
206
+ cleanup();
207
+ return 0;
208
+ }
209
+ close_requested_ = true;
210
+ }
211
+ signal_wakeup();
212
+ if (worker_.joinable()) {
213
+ worker_.join();
214
+ }
215
+ cleanup();
216
+ return 0;
217
+ }
218
+
219
+ int is_connected() const { return connected_ ? 1 : 0; }
220
+
221
+ const char *last_error() const { return last_error_str_.c_str(); }
222
+
223
+ int on_recv_stream_data(uint32_t flags, int64_t stream_id,
224
+ const uint8_t *data, size_t datalen) {
225
+ std::lock_guard<std::mutex> lock(stream_mutex_);
226
+ StreamState &state = streams_[stream_id];
227
+ state.recv_buf.insert(state.recv_buf.end(), data, data + datalen);
228
+ if (flags & NGTCP2_STREAM_DATA_FLAG_FIN) {
229
+ state.fin_received = true;
230
+ }
231
+ return 0;
232
+ }
233
+
234
+ int on_handshake_completed() {
235
+ {
236
+ std::lock_guard<std::mutex> lock(state_mutex_);
237
+ connected_ = true;
238
+ }
239
+ LOGI("ngtcp2 handshake completed");
240
+ cv_state_.notify_all();
241
+ return 0;
242
+ }
243
+
244
+ private:
245
+ static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
246
+ auto *client = static_cast<QuicClient *>(conn_ref->user_data);
247
+ return client->conn_;
248
+ }
249
+
250
+ int init_socket() {
251
+ struct addrinfo hints;
252
+ struct addrinfo *res = nullptr;
253
+ memset(&hints, 0, sizeof(hints));
254
+ hints.ai_socktype = SOCK_DGRAM;
255
+ hints.ai_family = AF_UNSPEC;
256
+
257
+ char port_str[16];
258
+ snprintf(port_str, sizeof(port_str), "%u", port_);
259
+ int rv = getaddrinfo(host_.c_str(), port_str, &hints, &res);
260
+ if (rv != 0) {
261
+ setError(gai_strerror(rv));
262
+ return -1;
263
+ }
264
+
265
+ int fd = -1;
266
+ for (auto *rp = res; rp; rp = rp->ai_next) {
267
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
268
+ if (fd == -1) {
269
+ continue;
270
+ }
271
+ if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
272
+ memcpy(&remote_addr_, rp->ai_addr, rp->ai_addrlen);
273
+ remote_addrlen_ = (socklen_t)rp->ai_addrlen;
274
+ break;
275
+ }
276
+ close(fd);
277
+ fd = -1;
278
+ }
279
+ freeaddrinfo(res);
280
+ if (fd == -1) {
281
+ setError("Failed to create/connect UDP socket");
282
+ return -1;
283
+ }
284
+
285
+ local_addrlen_ = sizeof(local_addr_);
286
+ if (getsockname(fd, (struct sockaddr *)&local_addr_, &local_addrlen_) !=
287
+ 0) {
288
+ setError("getsockname failed");
289
+ close(fd);
290
+ return -1;
291
+ }
292
+
293
+ int flags = fcntl(fd, F_GETFL, 0);
294
+ if (flags >= 0) {
295
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK);
296
+ }
297
+
298
+ fd_ = fd;
299
+ return 0;
300
+ }
301
+
302
+ int init_tls(const std::string &alpn) {
303
+ if (ngtcp2_crypto_quictls_init() != 0) {
304
+ setError("ngtcp2_crypto_quictls_init failed");
305
+ return -1;
306
+ }
307
+ ssl_ctx_ = SSL_CTX_new(TLS_client_method());
308
+ if (!ssl_ctx_) {
309
+ setError("SSL_CTX_new failed");
310
+ return -1;
311
+ }
312
+ if (ngtcp2_crypto_quictls_configure_client_context(ssl_ctx_) != 0) {
313
+ setError("ngtcp2_crypto_quictls_configure_client_context failed");
314
+ return -1;
315
+ }
316
+ SSL_CTX_set_verify(ssl_ctx_, SSL_VERIFY_PEER, nullptr);
317
+
318
+ ssl_ = SSL_new(ssl_ctx_);
319
+ if (!ssl_) {
320
+ setError("SSL_new failed");
321
+ return -1;
322
+ }
323
+ SSL_set_app_data(ssl_, &conn_ref_);
324
+ SSL_set_connect_state(ssl_);
325
+
326
+ std::string alpn_vec;
327
+ alpn_vec.push_back(static_cast<char>(alpn.size()));
328
+ alpn_vec.append(alpn);
329
+ SSL_set_alpn_protos(ssl_,
330
+ reinterpret_cast<const unsigned char *>(alpn_vec.data()),
331
+ (unsigned int)alpn_vec.size());
332
+ SSL_set_tlsext_host_name(ssl_, host_.c_str());
333
+
334
+ if (SSL_set1_host(ssl_, host_.c_str()) != 1) {
335
+ setError("SSL_set1_host failed");
336
+ return -1;
337
+ }
338
+
339
+ bool ca_loaded = false;
340
+ const char *ca_file = std::getenv("MQTT_QUIC_CA_FILE");
341
+ const char *ca_path = std::getenv("MQTT_QUIC_CA_PATH");
342
+ if ((ca_file && ca_file[0] != '\0') || (ca_path && ca_path[0] != '\0')) {
343
+ if (SSL_CTX_load_verify_locations(ssl_ctx_, ca_file, ca_path) != 1) {
344
+ setError("Failed to load CA bundle from MQTT_QUIC_CA_FILE/CA_PATH");
345
+ return -1;
346
+ }
347
+ ca_loaded = true;
348
+ }
349
+ if (!ca_loaded) {
350
+ if (SSL_CTX_set_default_verify_paths(ssl_ctx_) == 1) {
351
+ ca_loaded = true;
352
+ }
353
+ }
354
+ if (!ca_loaded) {
355
+ setError("No CA bundle available for TLS verification");
356
+ return -1;
357
+ }
358
+
359
+ return 0;
360
+ }
361
+
362
+ int init_quic() {
363
+ ngtcp2_callbacks callbacks;
364
+ memset(&callbacks, 0, sizeof(callbacks));
365
+ callbacks.client_initial = ngtcp2_crypto_client_initial_cb;
366
+ callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
367
+ callbacks.encrypt = ngtcp2_crypto_encrypt_cb;
368
+ callbacks.decrypt = ngtcp2_crypto_decrypt_cb;
369
+ callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb;
370
+ callbacks.recv_retry = ngtcp2_crypto_recv_retry_cb;
371
+ callbacks.update_key = ngtcp2_crypto_update_key_cb;
372
+ callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
373
+ callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
374
+ callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
375
+ callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
376
+ callbacks.handshake_completed = handshake_completed_cb;
377
+ callbacks.handshake_confirmed = handshake_completed_cb;
378
+ callbacks.recv_stream_data = recv_stream_data_cb;
379
+ callbacks.acked_stream_data_offset = acked_stream_data_offset_cb;
380
+ callbacks.stream_close = stream_close_cb;
381
+ callbacks.extend_max_local_streams_bidi = extend_max_local_streams_bidi_cb;
382
+ callbacks.rand = rand_cb;
383
+ callbacks.get_new_connection_id = get_new_connection_id_cb;
384
+
385
+ ngtcp2_settings settings;
386
+ ngtcp2_transport_params params;
387
+ ngtcp2_settings_default(&settings);
388
+ settings.initial_ts = now_ts();
389
+ settings.log_printf = log_printf;
390
+ settings.handshake_timeout = 10 * NGTCP2_SECONDS;
391
+
392
+ ngtcp2_transport_params_default(&params);
393
+ params.initial_max_streams_bidi = 8;
394
+ params.initial_max_stream_data_bidi_local = 256 * 1024;
395
+ params.initial_max_data = 1024 * 1024;
396
+
397
+ ngtcp2_cid dcid, scid;
398
+ dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN;
399
+ if (RAND_bytes(dcid.data, (int)dcid.datalen) != 1) {
400
+ setError("RAND_bytes failed");
401
+ return -1;
402
+ }
403
+ scid.datalen = 8;
404
+ if (RAND_bytes(scid.data, (int)scid.datalen) != 1) {
405
+ setError("RAND_bytes failed");
406
+ return -1;
407
+ }
408
+
409
+ ngtcp2_path path = {
410
+ .local = {.addr = (struct sockaddr *)&local_addr_,
411
+ .addrlen = local_addrlen_},
412
+ .remote = {.addr = (struct sockaddr *)&remote_addr_,
413
+ .addrlen = remote_addrlen_},
414
+ };
415
+
416
+ int rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path,
417
+ NGTCP2_PROTO_VER_V1, &callbacks, &settings,
418
+ &params, nullptr, this);
419
+ if (rv != 0) {
420
+ setError(ngtcp2_strerror(rv));
421
+ return -1;
422
+ }
423
+ ngtcp2_conn_set_tls_native_handle(conn_, ssl_);
424
+ return 0;
425
+ }
426
+
427
+ int init_wakeup_pipe() {
428
+ if (pipe(wakeup_fds_) != 0) {
429
+ setError("Failed to create wakeup pipe");
430
+ return -1;
431
+ }
432
+ int flags = fcntl(wakeup_fds_[0], F_GETFL, 0);
433
+ if (flags >= 0) {
434
+ fcntl(wakeup_fds_[0], F_SETFL, flags | O_NONBLOCK);
435
+ }
436
+ flags = fcntl(wakeup_fds_[1], F_GETFL, 0);
437
+ if (flags >= 0) {
438
+ fcntl(wakeup_fds_[1], F_SETFL, flags | O_NONBLOCK);
439
+ }
440
+ return 0;
441
+ }
442
+
443
+ void run_loop() {
444
+ send_pending_packets();
445
+ while (running_) {
446
+ int timeout_ms = compute_timeout_ms();
447
+ struct pollfd fds[2];
448
+ fds[0].fd = fd_;
449
+ fds[0].events = POLLIN;
450
+ fds[1].fd = wakeup_fds_[0];
451
+ fds[1].events = POLLIN;
452
+
453
+ int rv = poll(fds, 2, timeout_ms);
454
+ if (rv > 0) {
455
+ if (fds[1].revents & POLLIN) {
456
+ drain_wakeup();
457
+ }
458
+ if (fds[0].revents & POLLIN) {
459
+ if (read_packets() != 0) {
460
+ break;
461
+ }
462
+ }
463
+ }
464
+
465
+ if (handle_expiry() != 0) {
466
+ break;
467
+ }
468
+ if (send_pending_packets() != 0) {
469
+ break;
470
+ }
471
+
472
+ if (close_requested_) {
473
+ send_connection_close();
474
+ break;
475
+ }
476
+ }
477
+
478
+ running_ = false;
479
+ cv_state_.notify_all();
480
+ }
481
+
482
+ int compute_timeout_ms() {
483
+ if (!conn_) {
484
+ return 100;
485
+ }
486
+ uint64_t expiry = ngtcp2_conn_get_expiry(conn_);
487
+ uint64_t now = now_ts();
488
+ if (expiry <= now) {
489
+ return 0;
490
+ }
491
+ uint64_t delta_ms = (expiry - now) / (NGTCP2_MILLISECONDS);
492
+ if (delta_ms > 1000) {
493
+ return 1000;
494
+ }
495
+ return (int)delta_ms;
496
+ }
497
+
498
+ int read_packets() {
499
+ uint8_t buf[65536];
500
+ for (;;) {
501
+ ssize_t nread = recv(fd_, buf, sizeof(buf), 0);
502
+ if (nread <= 0) {
503
+ break;
504
+ }
505
+ ngtcp2_path path = {
506
+ .local = {.addr = (struct sockaddr *)&local_addr_,
507
+ .addrlen = local_addrlen_},
508
+ .remote = {.addr = (struct sockaddr *)&remote_addr_,
509
+ .addrlen = remote_addrlen_},
510
+ };
511
+ ngtcp2_pkt_info pi;
512
+ memset(&pi, 0, sizeof(pi));
513
+ int rv = ngtcp2_conn_read_pkt(conn_, &path, &pi, buf, (size_t)nread,
514
+ now_ts());
515
+ if (rv != 0) {
516
+ setError(ngtcp2_strerror(rv));
517
+ return -1;
518
+ }
519
+ }
520
+ return 0;
521
+ }
522
+
523
+ int handle_expiry() {
524
+ if (!conn_) {
525
+ return 0;
526
+ }
527
+ uint64_t now = now_ts();
528
+ uint64_t expiry = ngtcp2_conn_get_expiry(conn_);
529
+ if (expiry > now) {
530
+ return 0;
531
+ }
532
+ int rv = ngtcp2_conn_handle_expiry(conn_, now);
533
+ if (rv != 0) {
534
+ setError(ngtcp2_strerror(rv));
535
+ return -1;
536
+ }
537
+ return 0;
538
+ }
539
+
540
+ int send_pending_packets() {
541
+ if (!conn_) {
542
+ return 0;
543
+ }
544
+ for (;;) {
545
+ int64_t stream_id = -1;
546
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
547
+ ngtcp2_vec datav;
548
+ size_t datavcnt = 0;
549
+ bool fin = false;
550
+ {
551
+ std::lock_guard<std::mutex> lock(out_mutex_);
552
+ auto it = outgoing_.begin();
553
+ if (it != outgoing_.end() && !it->second.empty()) {
554
+ stream_id = it->first;
555
+ OutgoingChunk &chunk = it->second.front();
556
+ datav.base = chunk.data.data() + chunk.offset;
557
+ datav.len = chunk.data.size() - chunk.offset;
558
+ datavcnt = 1;
559
+ fin = chunk.fin;
560
+ }
561
+ }
562
+
563
+ if (fin) {
564
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
565
+ }
566
+
567
+ ngtcp2_path_storage ps;
568
+ ngtcp2_path_storage_zero(&ps);
569
+ ngtcp2_pkt_info pi;
570
+ ngtcp2_ssize nwrite = 0;
571
+ ngtcp2_ssize wdatalen = 0;
572
+ uint8_t buf[1452];
573
+ nwrite = ngtcp2_conn_writev_stream(conn_, &ps.path, &pi, buf, sizeof(buf),
574
+ &wdatalen, flags, stream_id,
575
+ datavcnt ? &datav : nullptr, datavcnt,
576
+ now_ts());
577
+ if (nwrite < 0) {
578
+ if (nwrite == NGTCP2_ERR_WRITE_MORE) {
579
+ std::lock_guard<std::mutex> lock(out_mutex_);
580
+ auto it = outgoing_.find(stream_id);
581
+ if (it != outgoing_.end() && !it->second.empty()) {
582
+ it->second.front().offset += (size_t)wdatalen;
583
+ if (it->second.front().offset >= it->second.front().data.size()) {
584
+ it->second.pop_front();
585
+ }
586
+ }
587
+ continue;
588
+ }
589
+ setError(ngtcp2_strerror((int)nwrite));
590
+ return -1;
591
+ }
592
+ if (nwrite == 0) {
593
+ return 0;
594
+ }
595
+
596
+ if (wdatalen > 0) {
597
+ std::lock_guard<std::mutex> lock(out_mutex_);
598
+ auto it = outgoing_.find(stream_id);
599
+ if (it != outgoing_.end() && !it->second.empty()) {
600
+ it->second.front().offset += (size_t)wdatalen;
601
+ if (it->second.front().offset >= it->second.front().data.size()) {
602
+ it->second.pop_front();
603
+ }
604
+ }
605
+ }
606
+
607
+ ssize_t nsend = send(fd_, buf, (size_t)nwrite, 0);
608
+ if (nsend < 0) {
609
+ setError("send failed");
610
+ return -1;
611
+ }
612
+ }
613
+ }
614
+
615
+ void send_connection_close() {
616
+ if (!conn_) {
617
+ return;
618
+ }
619
+ if (ngtcp2_conn_in_closing_period(conn_) ||
620
+ ngtcp2_conn_in_draining_period(conn_)) {
621
+ return;
622
+ }
623
+ uint8_t buf[1280];
624
+ ngtcp2_path_storage ps;
625
+ ngtcp2_path_storage_zero(&ps);
626
+ ngtcp2_pkt_info pi;
627
+ ngtcp2_ssize nwrite =
628
+ ngtcp2_conn_write_connection_close(conn_, &ps.path, &pi, buf,
629
+ sizeof(buf), &last_error_, now_ts());
630
+ if (nwrite > 0) {
631
+ send(fd_, buf, (size_t)nwrite, 0);
632
+ }
633
+ }
634
+
635
+ void signal_wakeup() {
636
+ if (wakeup_fds_[1] != -1) {
637
+ uint8_t b = 1;
638
+ write(wakeup_fds_[1], &b, 1);
639
+ }
640
+ }
641
+
642
+ void drain_wakeup() {
643
+ uint8_t buf[64];
644
+ while (read(wakeup_fds_[0], buf, sizeof(buf)) > 0) {
645
+ }
646
+ }
647
+
648
+ void cleanup() {
649
+ if (conn_) {
650
+ ngtcp2_conn_del(conn_);
651
+ conn_ = nullptr;
652
+ }
653
+ if (ssl_) {
654
+ SSL_free(ssl_);
655
+ ssl_ = nullptr;
656
+ }
657
+ if (ssl_ctx_) {
658
+ SSL_CTX_free(ssl_ctx_);
659
+ ssl_ctx_ = nullptr;
660
+ }
661
+ if (fd_ != -1) {
662
+ close(fd_);
663
+ fd_ = -1;
664
+ }
665
+ if (wakeup_fds_[0] != -1) {
666
+ close(wakeup_fds_[0]);
667
+ wakeup_fds_[0] = -1;
668
+ }
669
+ if (wakeup_fds_[1] != -1) {
670
+ close(wakeup_fds_[1]);
671
+ wakeup_fds_[1] = -1;
672
+ }
673
+ }
674
+
675
+ void clearError() {
676
+ std::lock_guard<std::mutex> lock(err_mutex_);
677
+ last_error_str_.clear();
678
+ }
679
+
680
+ void setError(const std::string &err) {
681
+ std::lock_guard<std::mutex> lock(err_mutex_);
682
+ last_error_str_ = err;
683
+ LOGE("%s", err.c_str());
684
+ }
685
+
686
+ static void rand_cb(uint8_t *dest, size_t destlen,
687
+ const ngtcp2_rand_ctx *rand_ctx) {
688
+ (void)rand_ctx;
689
+ if (RAND_bytes(dest, (int)destlen) != 1) {
690
+ abort();
691
+ }
692
+ }
693
+
694
+ static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
695
+ uint8_t *token, size_t cidlen,
696
+ void *user_data) {
697
+ (void)conn;
698
+ (void)user_data;
699
+ if (RAND_bytes(cid->data, (int)cidlen) != 1) {
700
+ return NGTCP2_ERR_CALLBACK_FAILURE;
701
+ }
702
+ cid->datalen = cidlen;
703
+ if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
704
+ return NGTCP2_ERR_CALLBACK_FAILURE;
705
+ }
706
+ return 0;
707
+ }
708
+
709
+ static int extend_max_local_streams_bidi_cb(ngtcp2_conn *conn,
710
+ uint64_t max_streams,
711
+ void *user_data) {
712
+ (void)conn;
713
+ (void)max_streams;
714
+ (void)user_data;
715
+ return 0;
716
+ }
717
+
718
+ static int recv_stream_data_cb(ngtcp2_conn *conn, uint32_t flags,
719
+ int64_t stream_id, uint64_t offset,
720
+ const uint8_t *data, size_t datalen,
721
+ void *user_data, void *stream_user_data) {
722
+ (void)conn;
723
+ (void)offset;
724
+ (void)stream_user_data;
725
+ auto *client = static_cast<QuicClient *>(user_data);
726
+ return client->on_recv_stream_data(flags, stream_id, data, datalen);
727
+ }
728
+
729
+ static int acked_stream_data_offset_cb(ngtcp2_conn *conn, int64_t stream_id,
730
+ uint64_t offset, uint64_t datalen,
731
+ void *user_data,
732
+ void *stream_user_data) {
733
+ (void)conn;
734
+ (void)stream_id;
735
+ (void)offset;
736
+ (void)datalen;
737
+ (void)user_data;
738
+ (void)stream_user_data;
739
+ return 0;
740
+ }
741
+
742
+ static int stream_close_cb(ngtcp2_conn *conn, int64_t stream_id,
743
+ uint64_t app_error_code, void *user_data,
744
+ void *stream_user_data) {
745
+ (void)conn;
746
+ (void)app_error_code;
747
+ (void)stream_user_data;
748
+ auto *client = static_cast<QuicClient *>(user_data);
749
+ std::lock_guard<std::mutex> lock(client->stream_mutex_);
750
+ auto it = client->streams_.find(stream_id);
751
+ if (it != client->streams_.end()) {
752
+ it->second.closed = true;
753
+ }
754
+ return 0;
755
+ }
756
+
757
+ static int handshake_completed_cb(ngtcp2_conn *conn, void *user_data) {
758
+ (void)conn;
759
+ auto *client = static_cast<QuicClient *>(user_data);
760
+ return client->on_handshake_completed();
761
+ }
762
+
763
+ private:
764
+ std::string host_;
765
+ uint16_t port_;
766
+
767
+ int fd_;
768
+ struct sockaddr_storage remote_addr_;
769
+ socklen_t remote_addrlen_;
770
+ struct sockaddr_storage local_addr_;
771
+ socklen_t local_addrlen_;
772
+
773
+ SSL_CTX *ssl_ctx_;
774
+ SSL *ssl_;
775
+ ngtcp2_conn *conn_;
776
+ ngtcp2_crypto_conn_ref conn_ref_;
777
+ ngtcp2_ccerr last_error_;
778
+
779
+ std::thread worker_;
780
+ std::atomic<bool> running_;
781
+ std::atomic<bool> connected_;
782
+ std::atomic<bool> close_requested_;
783
+
784
+ int wakeup_fds_[2];
785
+
786
+ std::mutex state_mutex_;
787
+ std::condition_variable cv_state_;
788
+
789
+ std::mutex stream_mutex_;
790
+ std::map<int64_t, StreamState> streams_;
791
+
792
+ std::mutex out_mutex_;
793
+ std::map<int64_t, std::deque<OutgoingChunk>> outgoing_;
794
+
795
+ mutable std::mutex err_mutex_;
796
+ std::string last_error_str_;
797
+ };
798
+
799
+ static std::map<jlong, std::unique_ptr<QuicClient>> connections;
800
+ static std::mutex connections_mutex;
801
+ static jlong next_handle = 1;
802
+
803
+ } // namespace
804
+
805
+ extern "C" {
806
+
807
+ JNIEXPORT jlong JNICALL
808
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeCreateConnection(
809
+ JNIEnv *env, jobject thiz, jstring host, jint port) {
810
+ const char *host_str = env->GetStringUTFChars(host, nullptr);
811
+ if (!host_str) {
812
+ return 0;
813
+ }
814
+ std::string host_cpp(host_str);
815
+ env->ReleaseStringUTFChars(host, host_str);
816
+
817
+ auto client = std::make_unique<QuicClient>(host_cpp, (uint16_t)port);
818
+ std::lock_guard<std::mutex> lock(connections_mutex);
819
+ jlong handle = next_handle++;
820
+ connections[handle] = std::move(client);
821
+ return handle;
822
+ }
823
+
824
+ JNIEXPORT jint JNICALL
825
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeConnect(
826
+ JNIEnv *env, jobject thiz, jlong connHandle) {
827
+ std::lock_guard<std::mutex> lock(connections_mutex);
828
+ auto it = connections.find(connHandle);
829
+ if (it == connections.end()) {
830
+ return -1;
831
+ }
832
+ int rv = it->second->connect("mqtt");
833
+ return rv;
834
+ }
835
+
836
+ JNIEXPORT jlong JNICALL
837
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeOpenStream(
838
+ JNIEnv *env, jobject thiz, jlong connHandle) {
839
+ std::lock_guard<std::mutex> lock(connections_mutex);
840
+ auto it = connections.find(connHandle);
841
+ if (it == connections.end()) {
842
+ return 0;
843
+ }
844
+ return it->second->open_stream();
845
+ }
846
+
847
+ JNIEXPORT jint JNICALL
848
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeWriteStream(
849
+ JNIEnv *env, jobject thiz, jlong connHandle, jlong streamId, jbyteArray data) {
850
+ std::lock_guard<std::mutex> lock(connections_mutex);
851
+ auto it = connections.find(connHandle);
852
+ if (it == connections.end()) {
853
+ return -1;
854
+ }
855
+ jsize len = env->GetArrayLength(data);
856
+ if (len <= 0) {
857
+ return 0;
858
+ }
859
+ std::vector<uint8_t> buffer((size_t)len);
860
+ env->GetByteArrayRegion(data, 0, len, reinterpret_cast<jbyte *>(buffer.data()));
861
+ return it->second->write_stream((int64_t)streamId, buffer.data(),
862
+ buffer.size(), false);
863
+ }
864
+
865
+ JNIEXPORT jbyteArray JNICALL
866
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeReadStream(
867
+ JNIEnv *env, jobject thiz, jlong connHandle, jlong streamId) {
868
+ std::lock_guard<std::mutex> lock(connections_mutex);
869
+ auto it = connections.find(connHandle);
870
+ if (it == connections.end()) {
871
+ return nullptr;
872
+ }
873
+ uint8_t buffer[8192];
874
+ ssize_t nread = it->second->read_stream((int64_t)streamId, buffer, sizeof(buffer));
875
+ if (nread <= 0) {
876
+ return env->NewByteArray(0);
877
+ }
878
+ jbyteArray result = env->NewByteArray((jsize)nread);
879
+ env->SetByteArrayRegion(result, 0, (jsize)nread, reinterpret_cast<jbyte *>(buffer));
880
+ return result;
881
+ }
882
+
883
+ JNIEXPORT void JNICALL
884
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeClose(
885
+ JNIEnv *env, jobject thiz, jlong connHandle) {
886
+ std::lock_guard<std::mutex> lock(connections_mutex);
887
+ auto it = connections.find(connHandle);
888
+ if (it == connections.end()) {
889
+ return;
890
+ }
891
+ it->second->close();
892
+ connections.erase(it);
893
+ }
894
+
895
+ JNIEXPORT jboolean JNICALL
896
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeIsConnected(
897
+ JNIEnv *env, jobject thiz, jlong connHandle) {
898
+ std::lock_guard<std::mutex> lock(connections_mutex);
899
+ auto it = connections.find(connHandle);
900
+ if (it == connections.end()) {
901
+ return JNI_FALSE;
902
+ }
903
+ return it->second->is_connected() ? JNI_TRUE : JNI_FALSE;
904
+ }
905
+
906
+ JNIEXPORT jint JNICALL
907
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeCloseStream(
908
+ JNIEnv *env, jobject thiz, jlong connHandle, jlong streamId) {
909
+ std::lock_guard<std::mutex> lock(connections_mutex);
910
+ auto it = connections.find(connHandle);
911
+ if (it == connections.end()) {
912
+ return -1;
913
+ }
914
+ return it->second->close_stream((int64_t)streamId);
915
+ }
916
+
917
+ JNIEXPORT jstring JNICALL
918
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeGetLastError(
919
+ JNIEnv *env, jobject thiz, jlong connHandle) {
920
+ std::lock_guard<std::mutex> lock(connections_mutex);
921
+ auto it = connections.find(connHandle);
922
+ if (it == connections.end()) {
923
+ return env->NewStringUTF("invalid connection");
924
+ }
925
+ return env->NewStringUTF(it->second->last_error());
926
+ }
927
+
928
+ } // extern "C"