librats 0.5.0 → 0.5.1

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 (63) hide show
  1. package/binding.gyp +1 -0
  2. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  3. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  4. package/native-src/CMakeLists.txt +360 -0
  5. package/native-src/LICENSE +21 -0
  6. package/native-src/src/bencode.cpp +485 -0
  7. package/native-src/src/bencode.h +145 -0
  8. package/native-src/src/bittorrent.cpp +3682 -0
  9. package/native-src/src/bittorrent.h +731 -0
  10. package/native-src/src/dht.cpp +2342 -0
  11. package/native-src/src/dht.h +501 -0
  12. package/native-src/src/encrypted_socket.cpp +817 -0
  13. package/native-src/src/encrypted_socket.h +239 -0
  14. package/native-src/src/file_transfer.cpp +1808 -0
  15. package/native-src/src/file_transfer.h +567 -0
  16. package/native-src/src/fs.cpp +639 -0
  17. package/native-src/src/fs.h +108 -0
  18. package/native-src/src/gossipsub.cpp +1137 -0
  19. package/native-src/src/gossipsub.h +403 -0
  20. package/native-src/src/ice.cpp +1386 -0
  21. package/native-src/src/ice.h +328 -0
  22. package/native-src/src/json.hpp +25526 -0
  23. package/native-src/src/krpc.cpp +558 -0
  24. package/native-src/src/krpc.h +145 -0
  25. package/native-src/src/librats.cpp +2715 -0
  26. package/native-src/src/librats.h +1729 -0
  27. package/native-src/src/librats_bittorrent.cpp +167 -0
  28. package/native-src/src/librats_c.cpp +1317 -0
  29. package/native-src/src/librats_c.h +237 -0
  30. package/native-src/src/librats_encryption.cpp +123 -0
  31. package/native-src/src/librats_file_transfer.cpp +226 -0
  32. package/native-src/src/librats_gossipsub.cpp +293 -0
  33. package/native-src/src/librats_ice.cpp +515 -0
  34. package/native-src/src/librats_logging.cpp +158 -0
  35. package/native-src/src/librats_mdns.cpp +171 -0
  36. package/native-src/src/librats_nat.cpp +571 -0
  37. package/native-src/src/librats_persistence.cpp +815 -0
  38. package/native-src/src/logger.h +412 -0
  39. package/native-src/src/mdns.cpp +1178 -0
  40. package/native-src/src/mdns.h +253 -0
  41. package/native-src/src/network_utils.cpp +598 -0
  42. package/native-src/src/network_utils.h +162 -0
  43. package/native-src/src/noise.cpp +981 -0
  44. package/native-src/src/noise.h +227 -0
  45. package/native-src/src/os.cpp +371 -0
  46. package/native-src/src/os.h +40 -0
  47. package/native-src/src/rats_export.h +17 -0
  48. package/native-src/src/sha1.cpp +163 -0
  49. package/native-src/src/sha1.h +42 -0
  50. package/native-src/src/socket.cpp +1376 -0
  51. package/native-src/src/socket.h +309 -0
  52. package/native-src/src/stun.cpp +484 -0
  53. package/native-src/src/stun.h +349 -0
  54. package/native-src/src/threadmanager.cpp +105 -0
  55. package/native-src/src/threadmanager.h +53 -0
  56. package/native-src/src/tracker.cpp +1110 -0
  57. package/native-src/src/tracker.h +268 -0
  58. package/native-src/src/version.cpp +24 -0
  59. package/native-src/src/version.h.in +45 -0
  60. package/native-src/version.rc.in +31 -0
  61. package/package.json +2 -8
  62. package/scripts/build-librats.js +59 -12
  63. package/scripts/prepare-package.js +133 -37
@@ -0,0 +1,1317 @@
1
+ #include "librats_c.h"
2
+ #include "librats.h"
3
+ #include "logger.h"
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+ #include <algorithm>
7
+ #include <cctype>
8
+ #include <unordered_map>
9
+
10
+ using namespace librats;
11
+
12
+ struct rats_client_wrapper {
13
+ std::unique_ptr<RatsClient> client;
14
+
15
+ // Stored C callbacks and user data
16
+ rats_connection_cb connection_cb = nullptr;
17
+ void* connection_ud = nullptr;
18
+
19
+ rats_string_cb string_cb = nullptr;
20
+ void* string_ud = nullptr;
21
+
22
+ rats_binary_cb binary_cb = nullptr;
23
+ void* binary_ud = nullptr;
24
+
25
+ rats_json_cb json_cb = nullptr;
26
+ void* json_ud = nullptr;
27
+
28
+ rats_disconnect_cb disconnect_cb = nullptr;
29
+ void* disconnect_ud = nullptr;
30
+
31
+ rats_peer_discovered_cb peer_discovered_cb = nullptr;
32
+ void* peer_discovered_ud = nullptr;
33
+
34
+ rats_file_progress_cb file_progress_cb = nullptr;
35
+ void* file_progress_ud = nullptr;
36
+
37
+ // File transfer callbacks
38
+ rats_file_request_cb file_request_cb = nullptr;
39
+ void* file_request_ud = nullptr;
40
+
41
+ rats_directory_request_cb directory_request_cb = nullptr;
42
+ void* directory_request_ud = nullptr;
43
+
44
+ rats_directory_progress_cb directory_progress_cb = nullptr;
45
+ void* directory_progress_ud = nullptr;
46
+
47
+ // Message handlers - using a simple map for demonstration
48
+ std::unordered_map<std::string, std::pair<rats_message_cb, void*>> message_handlers;
49
+
50
+ // GossipSub topic callbacks
51
+ std::unordered_map<std::string, std::pair<rats_topic_message_cb, void*>> topic_message_handlers;
52
+ std::unordered_map<std::string, std::pair<rats_topic_json_message_cb, void*>> topic_json_message_handlers;
53
+ std::unordered_map<std::string, std::pair<rats_topic_peer_joined_cb, void*>> topic_peer_joined_handlers;
54
+ std::unordered_map<std::string, std::pair<rats_topic_peer_left_cb, void*>> topic_peer_left_handlers;
55
+ };
56
+
57
+ static char* rats_strdup_owned(const std::string& s) {
58
+ size_t n = s.size();
59
+ char* out = static_cast<char*>(malloc(n + 1));
60
+ if (!out) return nullptr;
61
+ memcpy(out, s.c_str(), n);
62
+ out[n] = '\0';
63
+ return out;
64
+ }
65
+
66
+ extern "C" {
67
+
68
+ void rats_string_free(const char* str) {
69
+ if (str) free((void*)str);
70
+ }
71
+
72
+ const char* rats_get_version_string(void) {
73
+ return rats_get_library_version_string();
74
+ }
75
+
76
+ void rats_get_version(int* major, int* minor, int* patch, int* build) {
77
+ rats_get_library_version(major, minor, patch, build);
78
+ }
79
+
80
+ const char* rats_get_git_describe(void) {
81
+ return rats_get_library_git_describe();
82
+ }
83
+
84
+ uint32_t rats_get_abi(void) {
85
+ return rats_get_library_abi();
86
+ }
87
+
88
+ rats_client_t rats_create(int listen_port) {
89
+ try {
90
+ rats_client_wrapper* wrap = new rats_client_wrapper();
91
+ wrap->client = std::make_unique<RatsClient>(listen_port);
92
+ return static_cast<rats_client_t>(wrap);
93
+ } catch (...) {
94
+ return nullptr;
95
+ }
96
+ }
97
+
98
+ void rats_destroy(rats_client_t handle) {
99
+ if (!handle) return;
100
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
101
+ // Ensure stopped
102
+ wrap->client->stop();
103
+ delete wrap;
104
+ }
105
+
106
+ int rats_start(rats_client_t handle) {
107
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
108
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
109
+ return wrap->client->start() ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
110
+ }
111
+
112
+ void rats_stop(rats_client_t handle) {
113
+ if (!handle) return;
114
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
115
+ wrap->client->stop();
116
+ }
117
+
118
+ int rats_connect(rats_client_t handle, const char* host, int port) {
119
+ if (!handle || !host) return 0;
120
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
121
+ return wrap->client->connect_to_peer(std::string(host), port) ? 1 : 0;
122
+ }
123
+
124
+ int rats_get_listen_port(rats_client_t handle) {
125
+ if (!handle) return 0;
126
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
127
+ return wrap->client->get_listen_port();
128
+ }
129
+
130
+ int rats_broadcast_string(rats_client_t handle, const char* message) {
131
+ if (!handle || !message) return 0;
132
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
133
+ return wrap->client->broadcast_string_to_peers(std::string(message));
134
+ }
135
+
136
+ int rats_send_string(rats_client_t handle, const char* peer_id, const char* message) {
137
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
138
+ if (!peer_id || !message) return RATS_ERROR_INVALID_PARAMETER;
139
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
140
+ return wrap->client->send_string_to_peer_id(std::string(peer_id), std::string(message)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
141
+ }
142
+
143
+ int rats_get_peer_count(rats_client_t handle) {
144
+ if (!handle) return 0;
145
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
146
+ return wrap->client->get_peer_count();
147
+ }
148
+
149
+ char* rats_get_our_peer_id(rats_client_t handle) {
150
+ if (!handle) return nullptr;
151
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
152
+ return rats_strdup_owned(wrap->client->get_our_peer_id());
153
+ }
154
+
155
+ char* rats_get_connection_statistics_json(rats_client_t handle) {
156
+ if (!handle) return nullptr;
157
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
158
+ auto json = wrap->client->get_connection_statistics();
159
+ return rats_strdup_owned(json.dump());
160
+ }
161
+
162
+ void rats_set_logging_enabled(int enabled) {
163
+ // Global logger control through any client instance is awkward; use singleton
164
+ Logger::getInstance().set_file_logging_enabled(enabled != 0);
165
+ }
166
+
167
+ void rats_set_log_level(const char* level_str) {
168
+ if (!level_str) return;
169
+ std::string lvl(level_str);
170
+ // Map string to LogLevel
171
+ std::string upper = lvl;
172
+ std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
173
+ LogLevel level = LogLevel::INFO;
174
+ if (upper == "DEBUG") level = LogLevel::DEBUG;
175
+ else if (upper == "INFO") level = LogLevel::INFO;
176
+ else if (upper == "WARN" || upper == "WARNING") level = LogLevel::WARN;
177
+ else if (upper == "ERROR") level = LogLevel::ERROR;
178
+ Logger::getInstance().set_log_level(level);
179
+ }
180
+
181
+
182
+ void rats_set_log_file_path(rats_client_t handle, const char* file_path) {
183
+ if (!handle || !file_path) return;
184
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
185
+ wrap->client->set_log_file_path(std::string(file_path));
186
+ }
187
+
188
+ char* rats_get_log_file_path(rats_client_t handle) {
189
+ if (!handle) return nullptr;
190
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
191
+ return rats_strdup_owned(wrap->client->get_log_file_path());
192
+ }
193
+
194
+ void rats_set_log_colors_enabled(rats_client_t handle, int enabled) {
195
+ if (!handle) return;
196
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
197
+ wrap->client->set_log_colors_enabled(enabled != 0);
198
+ }
199
+
200
+ int rats_is_log_colors_enabled(rats_client_t handle) {
201
+ if (!handle) return 0;
202
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
203
+ return wrap->client->is_log_colors_enabled() ? 1 : 0;
204
+ }
205
+
206
+ void rats_set_log_timestamps_enabled(rats_client_t handle, int enabled) {
207
+ if (!handle) return;
208
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
209
+ wrap->client->set_log_timestamps_enabled(enabled != 0);
210
+ }
211
+
212
+ int rats_is_log_timestamps_enabled(rats_client_t handle) {
213
+ if (!handle) return 0;
214
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
215
+ return wrap->client->is_log_timestamps_enabled() ? 1 : 0;
216
+ }
217
+
218
+ void rats_set_log_rotation_size(rats_client_t handle, size_t max_size_bytes) {
219
+ if (!handle) return;
220
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
221
+ wrap->client->set_log_rotation_size(max_size_bytes);
222
+ }
223
+
224
+ void rats_set_log_retention_count(rats_client_t handle, int count) {
225
+ if (!handle) return;
226
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
227
+ wrap->client->set_log_retention_count(count);
228
+ }
229
+
230
+ void rats_clear_log_file(rats_client_t handle) {
231
+ if (!handle) return;
232
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
233
+ wrap->client->clear_log_file();
234
+ }
235
+
236
+ void rats_set_connection_callback(rats_client_t handle, rats_connection_cb cb, void* user_data) {
237
+ if (!handle) return;
238
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
239
+ wrap->connection_cb = cb;
240
+ wrap->connection_ud = user_data;
241
+ wrap->client->set_connection_callback([wrap](socket_t, const std::string& peer_id) {
242
+ if (wrap->connection_cb) {
243
+ wrap->connection_cb(wrap->connection_ud, peer_id.c_str());
244
+ }
245
+ });
246
+ }
247
+
248
+ void rats_set_string_callback(rats_client_t handle, rats_string_cb cb, void* user_data) {
249
+ if (!handle) return;
250
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
251
+ wrap->string_cb = cb;
252
+ wrap->string_ud = user_data;
253
+ wrap->client->set_string_data_callback([wrap](socket_t, const std::string& peer_id, const std::string& data) {
254
+ if (wrap->string_cb) {
255
+ wrap->string_cb(wrap->string_ud, peer_id.c_str(), data.c_str());
256
+ }
257
+ });
258
+ }
259
+
260
+ void rats_set_disconnect_callback(rats_client_t handle, rats_disconnect_cb cb, void* user_data) {
261
+ if (!handle) return;
262
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
263
+ wrap->disconnect_cb = cb;
264
+ wrap->disconnect_ud = user_data;
265
+ wrap->client->set_disconnect_callback([wrap](socket_t, const std::string& peer_id) {
266
+ if (wrap->disconnect_cb) {
267
+ wrap->disconnect_cb(wrap->disconnect_ud, peer_id.c_str());
268
+ }
269
+ });
270
+ }
271
+
272
+ // ===================== NEW C API IMPLEMENTATIONS =====================
273
+
274
+ // Peer configuration
275
+ rats_error_t rats_set_max_peers(rats_client_t handle, int max_peers) {
276
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
277
+ if (max_peers <= 0) return RATS_ERROR_INVALID_PARAMETER;
278
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
279
+ wrap->client->set_max_peers(max_peers);
280
+ return RATS_SUCCESS;
281
+ }
282
+
283
+ int rats_get_max_peers(rats_client_t handle) {
284
+ if (!handle) return 0;
285
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
286
+ return wrap->client->get_max_peers();
287
+ }
288
+
289
+ int rats_is_peer_limit_reached(rats_client_t handle) {
290
+ if (!handle) return 0;
291
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
292
+ return wrap->client->is_peer_limit_reached() ? 1 : 0;
293
+ }
294
+
295
+ // Advanced connection methods
296
+ rats_error_t rats_connect_with_strategy(rats_client_t handle, const char* host, int port,
297
+ rats_connection_strategy_t strategy) {
298
+ if (!handle || !host) return RATS_ERROR_INVALID_PARAMETER;
299
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
300
+
301
+ ConnectionStrategy cpp_strategy;
302
+ switch (strategy) {
303
+ case RATS_STRATEGY_DIRECT_ONLY: cpp_strategy = ConnectionStrategy::DIRECT_ONLY; break;
304
+ case RATS_STRATEGY_STUN_ASSISTED: cpp_strategy = ConnectionStrategy::STUN_ASSISTED; break;
305
+ case RATS_STRATEGY_ICE_FULL: cpp_strategy = ConnectionStrategy::ICE_FULL; break;
306
+ case RATS_STRATEGY_TURN_RELAY: cpp_strategy = ConnectionStrategy::TURN_RELAY; break;
307
+ case RATS_STRATEGY_AUTO_ADAPTIVE: cpp_strategy = ConnectionStrategy::AUTO_ADAPTIVE; break;
308
+ default: return RATS_ERROR_INVALID_PARAMETER;
309
+ }
310
+
311
+ return wrap->client->connect_to_peer(std::string(host), port, cpp_strategy) ?
312
+ RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
313
+ }
314
+
315
+ rats_error_t rats_disconnect_peer_by_id(rats_client_t handle, const char* peer_id) {
316
+ if (!handle || !peer_id) return RATS_ERROR_INVALID_PARAMETER;
317
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
318
+ wrap->client->disconnect_peer_by_id(std::string(peer_id));
319
+ return RATS_SUCCESS;
320
+ }
321
+
322
+ // Binary data operations
323
+ rats_error_t rats_send_binary(rats_client_t handle, const char* peer_id, const void* data, size_t size) {
324
+ if (!handle || !peer_id || !data || size == 0) return RATS_ERROR_INVALID_PARAMETER;
325
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
326
+
327
+ std::vector<uint8_t> binary_data(static_cast<const uint8_t*>(data),
328
+ static_cast<const uint8_t*>(data) + size);
329
+
330
+ return wrap->client->send_binary_to_peer_id(std::string(peer_id), binary_data) ?
331
+ RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
332
+ }
333
+
334
+ int rats_broadcast_binary(rats_client_t handle, const void* data, size_t size) {
335
+ if (!handle || !data || size == 0) return 0;
336
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
337
+
338
+ std::vector<uint8_t> binary_data(static_cast<const uint8_t*>(data),
339
+ static_cast<const uint8_t*>(data) + size);
340
+
341
+ return wrap->client->broadcast_binary_to_peers(binary_data);
342
+ }
343
+
344
+ // JSON operations
345
+ rats_error_t rats_send_json(rats_client_t handle, const char* peer_id, const char* json_str) {
346
+ if (!handle || !peer_id || !json_str) return RATS_ERROR_INVALID_PARAMETER;
347
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
348
+
349
+ try {
350
+ nlohmann::json json_data = nlohmann::json::parse(json_str);
351
+ return wrap->client->send_json_to_peer_id(std::string(peer_id), json_data) ?
352
+ RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
353
+ } catch (const nlohmann::json::exception&) {
354
+ return RATS_ERROR_JSON_PARSE;
355
+ }
356
+ }
357
+
358
+ int rats_broadcast_json(rats_client_t handle, const char* json_str) {
359
+ if (!handle || !json_str) return 0;
360
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
361
+
362
+ try {
363
+ nlohmann::json json_data = nlohmann::json::parse(json_str);
364
+ return wrap->client->broadcast_json_to_peers(json_data);
365
+ } catch (const nlohmann::json::exception&) {
366
+ return 0;
367
+ }
368
+ }
369
+
370
+ // DHT Discovery
371
+ rats_error_t rats_start_dht_discovery(rats_client_t handle, int dht_port) {
372
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
373
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
374
+ return wrap->client->start_dht_discovery(dht_port) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
375
+ }
376
+
377
+ void rats_stop_dht_discovery(rats_client_t handle) {
378
+ if (!handle) return;
379
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
380
+ wrap->client->stop_dht_discovery();
381
+ }
382
+
383
+ int rats_is_dht_running(rats_client_t handle) {
384
+ if (!handle) return 0;
385
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
386
+ return wrap->client->is_dht_running() ? 1 : 0;
387
+ }
388
+
389
+ rats_error_t rats_announce_for_hash(rats_client_t handle, const char* content_hash, int port) {
390
+ if (!handle || !content_hash) return RATS_ERROR_INVALID_PARAMETER;
391
+ if (strlen(content_hash) != 40) return RATS_ERROR_INVALID_PARAMETER; // SHA1 hash must be 40 chars
392
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
393
+
394
+ uint16_t announce_port = (port <= 0) ? 0 : static_cast<uint16_t>(port);
395
+ return wrap->client->announce_for_hash(std::string(content_hash), announce_port) ?
396
+ RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
397
+ }
398
+
399
+ size_t rats_get_dht_routing_table_size(rats_client_t handle) {
400
+ if (!handle) return 0;
401
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
402
+ return wrap->client->get_dht_routing_table_size();
403
+ }
404
+
405
+ // Automatic discovery
406
+
407
+ void rats_start_automatic_peer_discovery(rats_client_t handle) {
408
+ if (!handle) return;
409
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
410
+ wrap->client->start_automatic_peer_discovery();
411
+ }
412
+
413
+ void rats_stop_automatic_peer_discovery(rats_client_t handle) {
414
+ if (!handle) return;
415
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
416
+ wrap->client->stop_automatic_peer_discovery();
417
+ }
418
+
419
+ int rats_is_automatic_discovery_running(rats_client_t handle) {
420
+ if (!handle) return 0;
421
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
422
+ return wrap->client->is_automatic_discovery_running() ? 1 : 0;
423
+ }
424
+
425
+ char* rats_get_discovery_hash(rats_client_t handle) {
426
+ if (!handle) return nullptr;
427
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
428
+ return rats_strdup_owned(wrap->client->get_discovery_hash());
429
+ }
430
+
431
+ char* rats_get_rats_peer_discovery_hash(void) {
432
+ return rats_strdup_owned(RatsClient::get_rats_peer_discovery_hash());
433
+ }
434
+
435
+ // mDNS Discovery
436
+ rats_error_t rats_start_mdns_discovery(rats_client_t handle, const char* service_name) {
437
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
438
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
439
+
440
+ std::string service = service_name ? std::string(service_name) : std::string("");
441
+ return wrap->client->start_mdns_discovery(service) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
442
+ }
443
+
444
+ void rats_stop_mdns_discovery(rats_client_t handle) {
445
+ if (!handle) return;
446
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
447
+ wrap->client->stop_mdns_discovery();
448
+ }
449
+
450
+ int rats_is_mdns_running(rats_client_t handle) {
451
+ if (!handle) return 0;
452
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
453
+ return wrap->client->is_mdns_running() ? 1 : 0;
454
+ }
455
+
456
+ rats_error_t rats_query_mdns_services(rats_client_t handle) {
457
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
458
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
459
+ return wrap->client->query_mdns_services() ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
460
+ }
461
+
462
+ // Encryption
463
+ rats_error_t rats_set_encryption_enabled(rats_client_t handle, int enabled) {
464
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
465
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
466
+ wrap->client->set_encryption_enabled(enabled != 0);
467
+ return RATS_SUCCESS;
468
+ }
469
+
470
+ int rats_is_encryption_enabled(rats_client_t handle) {
471
+ if (!handle) return 0;
472
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
473
+ return wrap->client->is_encryption_enabled() ? 1 : 0;
474
+ }
475
+
476
+ char* rats_get_encryption_key(rats_client_t handle) {
477
+ if (!handle) return nullptr;
478
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
479
+ return rats_strdup_owned(wrap->client->get_encryption_key());
480
+ }
481
+
482
+ rats_error_t rats_set_encryption_key(rats_client_t handle, const char* key_hex) {
483
+ if (!handle || !key_hex) return RATS_ERROR_INVALID_PARAMETER;
484
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
485
+ return wrap->client->set_encryption_key(std::string(key_hex)) ?
486
+ RATS_SUCCESS : RATS_ERROR_INVALID_PARAMETER;
487
+ }
488
+
489
+ char* rats_generate_encryption_key(rats_client_t handle) {
490
+ if (!handle) return nullptr;
491
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
492
+ return rats_strdup_owned(wrap->client->generate_new_encryption_key());
493
+ }
494
+
495
+ // Protocol configuration
496
+ rats_error_t rats_set_protocol_name(rats_client_t handle, const char* protocol_name) {
497
+ if (!handle || !protocol_name) return RATS_ERROR_INVALID_PARAMETER;
498
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
499
+ wrap->client->set_protocol_name(std::string(protocol_name));
500
+ return RATS_SUCCESS;
501
+ }
502
+
503
+ rats_error_t rats_set_protocol_version(rats_client_t handle, const char* protocol_version) {
504
+ if (!handle || !protocol_version) return RATS_ERROR_INVALID_PARAMETER;
505
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
506
+ wrap->client->set_protocol_version(std::string(protocol_version));
507
+ return RATS_SUCCESS;
508
+ }
509
+
510
+ char* rats_get_protocol_name(rats_client_t handle) {
511
+ if (!handle) return nullptr;
512
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
513
+ return rats_strdup_owned(wrap->client->get_protocol_name());
514
+ }
515
+
516
+ char* rats_get_protocol_version(rats_client_t handle) {
517
+ if (!handle) return nullptr;
518
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
519
+ return rats_strdup_owned(wrap->client->get_protocol_version());
520
+ }
521
+
522
+ // Message Exchange API
523
+ rats_error_t rats_on_message(rats_client_t handle, const char* message_type,
524
+ rats_message_cb callback, void* user_data) {
525
+ if (!handle || !message_type || !callback) return RATS_ERROR_INVALID_PARAMETER;
526
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
527
+
528
+ // Store the C callback
529
+ wrap->message_handlers[std::string(message_type)] = std::make_pair(callback, user_data);
530
+
531
+ // Set up C++ callback that calls the C callback
532
+ wrap->client->on(std::string(message_type), [wrap, callback, user_data](const std::string& peer_id, const nlohmann::json& data) {
533
+ if (callback) {
534
+ std::string json_str = data.dump();
535
+ callback(user_data, peer_id.c_str(), json_str.c_str());
536
+ }
537
+ });
538
+
539
+ return RATS_SUCCESS;
540
+ }
541
+
542
+ rats_error_t rats_send_message(rats_client_t handle, const char* peer_id,
543
+ const char* message_type, const char* data) {
544
+ if (!handle || !peer_id || !message_type || !data) return RATS_ERROR_INVALID_PARAMETER;
545
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
546
+
547
+ try {
548
+ nlohmann::json json_data = nlohmann::json::parse(data);
549
+ wrap->client->send(std::string(peer_id), std::string(message_type), json_data);
550
+ return RATS_SUCCESS;
551
+ } catch (const nlohmann::json::exception&) {
552
+ // If not valid JSON, send as string value
553
+ nlohmann::json string_data = std::string(data);
554
+ wrap->client->send(std::string(peer_id), std::string(message_type), string_data);
555
+ return RATS_SUCCESS;
556
+ }
557
+ }
558
+
559
+ rats_error_t rats_broadcast_message(rats_client_t handle, const char* message_type, const char* data) {
560
+ if (!handle || !message_type || !data) return RATS_ERROR_INVALID_PARAMETER;
561
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
562
+
563
+ try {
564
+ nlohmann::json json_data = nlohmann::json::parse(data);
565
+ wrap->client->send(std::string(message_type), json_data);
566
+ return RATS_SUCCESS;
567
+ } catch (const nlohmann::json::exception&) {
568
+ // If not valid JSON, send as string value
569
+ nlohmann::json string_data = std::string(data);
570
+ wrap->client->send(std::string(message_type), string_data);
571
+ return RATS_SUCCESS;
572
+ }
573
+ }
574
+
575
+ // File Transfer
576
+ char* rats_send_file(rats_client_t handle, const char* peer_id,
577
+ const char* file_path, const char* remote_filename) {
578
+ if (!handle || !peer_id || !file_path) return nullptr;
579
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
580
+
581
+ std::string remote_name = remote_filename ? std::string(remote_filename) : std::string("");
582
+ std::string transfer_id = wrap->client->send_file(std::string(peer_id),
583
+ std::string(file_path), remote_name);
584
+
585
+ return transfer_id.empty() ? nullptr : rats_strdup_owned(transfer_id);
586
+ }
587
+
588
+ rats_error_t rats_accept_file_transfer(rats_client_t handle, const char* transfer_id,
589
+ const char* local_path) {
590
+ if (!handle || !transfer_id || !local_path) return RATS_ERROR_INVALID_PARAMETER;
591
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
592
+
593
+ return wrap->client->accept_file_transfer(std::string(transfer_id), std::string(local_path)) ?
594
+ RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
595
+ }
596
+
597
+ rats_error_t rats_reject_file_transfer(rats_client_t handle, const char* transfer_id,
598
+ const char* reason) {
599
+ if (!handle || !transfer_id) return RATS_ERROR_INVALID_PARAMETER;
600
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
601
+
602
+ std::string reject_reason = reason ? std::string(reason) : std::string("");
603
+ return wrap->client->reject_file_transfer(std::string(transfer_id), reject_reason) ?
604
+ RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
605
+ }
606
+
607
+ rats_error_t rats_cancel_file_transfer(rats_client_t handle, const char* transfer_id) {
608
+ if (!handle || !transfer_id) return RATS_ERROR_INVALID_PARAMETER;
609
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
610
+
611
+ return wrap->client->cancel_file_transfer(std::string(transfer_id)) ?
612
+ RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
613
+ }
614
+
615
+ // Enhanced callbacks
616
+ void rats_set_binary_callback(rats_client_t handle, rats_binary_cb cb, void* user_data) {
617
+ if (!handle) return;
618
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
619
+ wrap->binary_cb = cb;
620
+ wrap->binary_ud = user_data;
621
+ wrap->client->set_binary_data_callback([wrap](socket_t, const std::string& peer_id, const std::vector<uint8_t>& data) {
622
+ if (wrap->binary_cb) {
623
+ wrap->binary_cb(wrap->binary_ud, peer_id.c_str(), data.data(), data.size());
624
+ }
625
+ });
626
+ }
627
+
628
+ void rats_set_json_callback(rats_client_t handle, rats_json_cb cb, void* user_data) {
629
+ if (!handle) return;
630
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
631
+ wrap->json_cb = cb;
632
+ wrap->json_ud = user_data;
633
+ wrap->client->set_json_data_callback([wrap](socket_t, const std::string& peer_id, const nlohmann::json& data) {
634
+ if (wrap->json_cb) {
635
+ std::string json_str = data.dump();
636
+ wrap->json_cb(wrap->json_ud, peer_id.c_str(), json_str.c_str());
637
+ }
638
+ });
639
+ }
640
+
641
+ void rats_set_peer_discovered_callback(rats_client_t handle, rats_peer_discovered_cb cb, void* user_data) {
642
+ if (!handle) return;
643
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
644
+ wrap->peer_discovered_cb = cb;
645
+ wrap->peer_discovered_ud = user_data;
646
+ wrap->client->set_mdns_callback([wrap](const std::string& host, int port, const std::string& service_name) {
647
+ if (wrap->peer_discovered_cb) {
648
+ wrap->peer_discovered_cb(wrap->peer_discovered_ud, host.c_str(), port, service_name.c_str());
649
+ }
650
+ });
651
+ }
652
+
653
+ void rats_set_file_progress_callback(rats_client_t handle, rats_file_progress_cb cb, void* user_data) {
654
+ if (!handle) return;
655
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
656
+ wrap->file_progress_cb = cb;
657
+ wrap->file_progress_ud = user_data;
658
+
659
+ wrap->client->on_file_transfer_progress([wrap](const FileTransferProgress& progress) {
660
+ if (wrap->file_progress_cb) {
661
+ int progress_percent = progress.get_completion_percentage();
662
+ std::string status_str = "IN_PROGRESS"; // Default status
663
+ switch (progress.status) {
664
+ case FileTransferStatus::PENDING: status_str = "PENDING"; break;
665
+ case FileTransferStatus::STARTING: status_str = "STARTING"; break;
666
+ case FileTransferStatus::IN_PROGRESS: status_str = "IN_PROGRESS"; break;
667
+ case FileTransferStatus::PAUSED: status_str = "PAUSED"; break;
668
+ case FileTransferStatus::COMPLETED: status_str = "COMPLETED"; break;
669
+ case FileTransferStatus::FAILED: status_str = "FAILED"; break;
670
+ case FileTransferStatus::CANCELLED: status_str = "CANCELLED"; break;
671
+ case FileTransferStatus::RESUMING: status_str = "RESUMING"; break;
672
+ }
673
+ wrap->file_progress_cb(wrap->file_progress_ud, progress.transfer_id.c_str(), progress_percent, status_str.c_str());
674
+ }
675
+ });
676
+ }
677
+
678
+ char* rats_send_directory(rats_client_t handle, const char* peer_id, const char* directory_path, const char* remote_directory_name, int recursive) {
679
+ if (!handle || !peer_id || !directory_path) return nullptr;
680
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
681
+
682
+ std::string remote_name = remote_directory_name ? std::string(remote_directory_name) : std::string("");
683
+ std::string transfer_id = wrap->client->send_directory(std::string(peer_id), std::string(directory_path), remote_name, recursive != 0);
684
+
685
+ return transfer_id.empty() ? nullptr : rats_strdup_owned(transfer_id);
686
+ }
687
+
688
+ char* rats_request_file(rats_client_t handle, const char* peer_id, const char* remote_file_path, const char* local_path) {
689
+ if (!handle || !peer_id || !remote_file_path || !local_path) return nullptr;
690
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
691
+
692
+ std::string transfer_id = wrap->client->request_file(std::string(peer_id), std::string(remote_file_path), std::string(local_path));
693
+
694
+ return transfer_id.empty() ? nullptr : rats_strdup_owned(transfer_id);
695
+ }
696
+
697
+ char* rats_request_directory(rats_client_t handle, const char* peer_id, const char* remote_directory_path, const char* local_directory_path, int recursive) {
698
+ if (!handle || !peer_id || !remote_directory_path || !local_directory_path) return nullptr;
699
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
700
+
701
+ std::string transfer_id = wrap->client->request_directory(std::string(peer_id), std::string(remote_directory_path), std::string(local_directory_path), recursive != 0);
702
+
703
+ return transfer_id.empty() ? nullptr : rats_strdup_owned(transfer_id);
704
+ }
705
+
706
+ rats_error_t rats_accept_directory_transfer(rats_client_t handle, const char* transfer_id, const char* local_path) {
707
+ if (!handle || !transfer_id || !local_path) return RATS_ERROR_INVALID_PARAMETER;
708
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
709
+
710
+ return wrap->client->accept_directory_transfer(std::string(transfer_id), std::string(local_path)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
711
+ }
712
+
713
+ rats_error_t rats_reject_directory_transfer(rats_client_t handle, const char* transfer_id, const char* reason) {
714
+ if (!handle || !transfer_id) return RATS_ERROR_INVALID_PARAMETER;
715
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
716
+
717
+ std::string reject_reason = reason ? std::string(reason) : std::string("");
718
+ return wrap->client->reject_directory_transfer(std::string(transfer_id), reject_reason) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
719
+ }
720
+
721
+ rats_error_t rats_pause_file_transfer(rats_client_t handle, const char* transfer_id) {
722
+ if (!handle || !transfer_id) return RATS_ERROR_INVALID_PARAMETER;
723
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
724
+
725
+ return wrap->client->pause_file_transfer(std::string(transfer_id)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
726
+ }
727
+
728
+ rats_error_t rats_resume_file_transfer(rats_client_t handle, const char* transfer_id) {
729
+ if (!handle || !transfer_id) return RATS_ERROR_INVALID_PARAMETER;
730
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
731
+
732
+ return wrap->client->resume_file_transfer(std::string(transfer_id)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
733
+ }
734
+
735
+ char* rats_get_file_transfer_progress_json(rats_client_t handle, const char* transfer_id) {
736
+ if (!handle || !transfer_id) return nullptr;
737
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
738
+
739
+ auto progress = wrap->client->get_file_transfer_progress(std::string(transfer_id));
740
+ if (!progress) return nullptr;
741
+
742
+ // Convert progress to JSON
743
+ nlohmann::json progress_json;
744
+ progress_json["transfer_id"] = progress->transfer_id;
745
+ progress_json["filename"] = progress->filename;
746
+ progress_json["local_path"] = progress->local_path;
747
+ progress_json["peer_id"] = progress->peer_id;
748
+ progress_json["bytes_transferred"] = progress->bytes_transferred;
749
+ progress_json["total_bytes"] = progress->total_bytes;
750
+ progress_json["completion_percentage"] = progress->get_completion_percentage();
751
+ progress_json["status"] = static_cast<int>(progress->status);
752
+ progress_json["direction"] = static_cast<int>(progress->direction);
753
+
754
+ return rats_strdup_owned(progress_json.dump());
755
+ }
756
+
757
+ char* rats_get_file_transfer_statistics_json(rats_client_t handle) {
758
+ if (!handle) return nullptr;
759
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
760
+ auto stats = wrap->client->get_file_transfer_statistics();
761
+ return rats_strdup_owned(stats.dump());
762
+ }
763
+
764
+ // File transfer callback setters
765
+ void rats_set_file_request_callback(rats_client_t handle, rats_file_request_cb cb, void* user_data) {
766
+ if (!handle) return;
767
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
768
+ wrap->file_request_cb = cb;
769
+ wrap->file_request_ud = user_data;
770
+
771
+ if (cb) {
772
+ wrap->client->on_file_request([wrap](const std::string& peer_id, const std::string& file_path, const std::string& transfer_id) {
773
+ if (wrap->file_request_cb) {
774
+ wrap->file_request_cb(wrap->file_request_ud, peer_id.c_str(), transfer_id.c_str(), file_path.c_str(), "");
775
+ }
776
+ return true; // Accept the file request
777
+ });
778
+ }
779
+ }
780
+
781
+ void rats_set_directory_request_callback(rats_client_t handle, rats_directory_request_cb cb, void* user_data) {
782
+ if (!handle) return;
783
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
784
+ wrap->directory_request_cb = cb;
785
+ wrap->directory_request_ud = user_data;
786
+
787
+ if (cb) {
788
+ wrap->client->on_directory_request([wrap](const std::string& peer_id, const std::string& directory_path, bool recursive, const std::string& transfer_id) {
789
+ if (wrap->directory_request_cb) {
790
+ wrap->directory_request_cb(wrap->directory_request_ud, peer_id.c_str(), transfer_id.c_str(), directory_path.c_str(), "");
791
+ }
792
+ return true; // Accept the directory request
793
+ });
794
+ }
795
+ }
796
+
797
+ void rats_set_directory_progress_callback(rats_client_t handle, rats_directory_progress_cb cb, void* user_data) {
798
+ if (!handle) return;
799
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
800
+ wrap->directory_progress_cb = cb;
801
+ wrap->directory_progress_ud = user_data;
802
+
803
+ if (cb) {
804
+ wrap->client->on_directory_transfer_progress([wrap](const std::string& transfer_id, const std::string& current_file, uint64_t files_completed, uint64_t total_files, uint64_t bytes_completed, uint64_t total_bytes) {
805
+ if (wrap->directory_progress_cb) {
806
+ wrap->directory_progress_cb(wrap->directory_progress_ud, transfer_id.c_str(), static_cast<int>(files_completed), static_cast<int>(total_files), current_file.c_str());
807
+ }
808
+ });
809
+ }
810
+ }
811
+
812
+ // Peer information
813
+ char** rats_get_peer_ids(rats_client_t handle, int* count) {
814
+ if (!handle || !count) {
815
+ if (count) *count = 0;
816
+ return nullptr;
817
+ }
818
+
819
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
820
+ auto peers = wrap->client->get_validated_peers();
821
+
822
+ *count = static_cast<int>(peers.size());
823
+ if (*count == 0) return nullptr;
824
+
825
+ char** peer_ids = static_cast<char**>(malloc(*count * sizeof(char*)));
826
+ if (!peer_ids) {
827
+ *count = 0;
828
+ return nullptr;
829
+ }
830
+
831
+ for (int i = 0; i < *count; ++i) {
832
+ peer_ids[i] = rats_strdup_owned(peers[i].peer_id);
833
+ if (!peer_ids[i]) {
834
+ // Clean up on allocation failure
835
+ for (int j = 0; j < i; ++j) {
836
+ free(peer_ids[j]);
837
+ }
838
+ free(peer_ids);
839
+ *count = 0;
840
+ return nullptr;
841
+ }
842
+ }
843
+
844
+ return peer_ids;
845
+ }
846
+
847
+ char* rats_get_peer_info_json(rats_client_t handle, const char* peer_id) {
848
+ if (!handle || !peer_id) return nullptr;
849
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
850
+
851
+ const RatsPeer* peer = wrap->client->get_peer_by_id(std::string(peer_id));
852
+ if (!peer) return nullptr;
853
+
854
+ // Create JSON representation of peer info
855
+ nlohmann::json peer_info;
856
+ peer_info["peer_id"] = peer->peer_id;
857
+ peer_info["ip"] = peer->ip;
858
+ peer_info["port"] = peer->port;
859
+ peer_info["is_outgoing"] = peer->is_outgoing;
860
+ peer_info["handshake_completed"] = peer->is_handshake_completed();
861
+ peer_info["version"] = peer->version;
862
+ peer_info["encryption_enabled"] = peer->encryption_enabled;
863
+
864
+ return rats_strdup_owned(peer_info.dump());
865
+ }
866
+
867
+ char** rats_get_validated_peer_ids(rats_client_t handle, int* count) {
868
+ if (!handle || !count) {
869
+ if (count) *count = 0;
870
+ return nullptr;
871
+ }
872
+
873
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
874
+ auto peers = wrap->client->get_validated_peers();
875
+
876
+ *count = static_cast<int>(peers.size());
877
+ if (*count == 0) return nullptr;
878
+
879
+ char** peer_ids = static_cast<char**>(malloc(*count * sizeof(char*)));
880
+ if (!peer_ids) {
881
+ *count = 0;
882
+ return nullptr;
883
+ }
884
+
885
+ for (int i = 0; i < *count; ++i) {
886
+ peer_ids[i] = rats_strdup_owned(peers[i].peer_id);
887
+ if (!peer_ids[i]) {
888
+ // Clean up on allocation failure
889
+ for (int j = 0; j < i; ++j) {
890
+ free(peer_ids[j]);
891
+ }
892
+ free(peer_ids);
893
+ *count = 0;
894
+ return nullptr;
895
+ }
896
+ }
897
+
898
+ return peer_ids;
899
+ }
900
+
901
+ // ===================== GOSSIPSUB FUNCTIONALITY =====================
902
+
903
+ int rats_is_gossipsub_available(rats_client_t handle) {
904
+ if (!handle) return 0;
905
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
906
+ return wrap->client->is_gossipsub_available() ? 1 : 0;
907
+ }
908
+
909
+ int rats_is_gossipsub_running(rats_client_t handle) {
910
+ if (!handle) return 0;
911
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
912
+ return wrap->client->is_gossipsub_running() ? 1 : 0;
913
+ }
914
+
915
+ rats_error_t rats_subscribe_to_topic(rats_client_t handle, const char* topic) {
916
+ if (!handle || !topic) return RATS_ERROR_INVALID_PARAMETER;
917
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
918
+ return wrap->client->subscribe_to_topic(std::string(topic)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
919
+ }
920
+
921
+ rats_error_t rats_unsubscribe_from_topic(rats_client_t handle, const char* topic) {
922
+ if (!handle || !topic) return RATS_ERROR_INVALID_PARAMETER;
923
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
924
+ return wrap->client->unsubscribe_from_topic(std::string(topic)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
925
+ }
926
+
927
+ int rats_is_subscribed_to_topic(rats_client_t handle, const char* topic) {
928
+ if (!handle || !topic) return 0;
929
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
930
+ return wrap->client->is_subscribed_to_topic(std::string(topic)) ? 1 : 0;
931
+ }
932
+
933
+ char** rats_get_subscribed_topics(rats_client_t handle, int* count) {
934
+ if (!handle || !count) {
935
+ if (count) *count = 0;
936
+ return nullptr;
937
+ }
938
+
939
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
940
+ auto topics = wrap->client->get_subscribed_topics();
941
+
942
+ *count = static_cast<int>(topics.size());
943
+ if (*count == 0) return nullptr;
944
+
945
+ char** topic_array = static_cast<char**>(malloc(*count * sizeof(char*)));
946
+ if (!topic_array) {
947
+ *count = 0;
948
+ return nullptr;
949
+ }
950
+
951
+ for (int i = 0; i < *count; ++i) {
952
+ topic_array[i] = rats_strdup_owned(topics[i]);
953
+ if (!topic_array[i]) {
954
+ // Clean up on allocation failure
955
+ for (int j = 0; j < i; ++j) {
956
+ free(topic_array[j]);
957
+ }
958
+ free(topic_array);
959
+ *count = 0;
960
+ return nullptr;
961
+ }
962
+ }
963
+
964
+ return topic_array;
965
+ }
966
+
967
+ rats_error_t rats_publish_to_topic(rats_client_t handle, const char* topic, const char* message) {
968
+ if (!handle || !topic || !message) return RATS_ERROR_INVALID_PARAMETER;
969
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
970
+ return wrap->client->publish_to_topic(std::string(topic), std::string(message)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
971
+ }
972
+
973
+ rats_error_t rats_publish_json_to_topic(rats_client_t handle, const char* topic, const char* json_str) {
974
+ if (!handle || !topic || !json_str) return RATS_ERROR_INVALID_PARAMETER;
975
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
976
+
977
+ try {
978
+ nlohmann::json json_data = nlohmann::json::parse(json_str);
979
+ return wrap->client->publish_json_to_topic(std::string(topic), json_data) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
980
+ } catch (const nlohmann::json::exception&) {
981
+ return RATS_ERROR_JSON_PARSE;
982
+ }
983
+ }
984
+
985
+ char** rats_get_topic_peers(rats_client_t handle, const char* topic, int* count) {
986
+ if (!handle || !topic || !count) {
987
+ if (count) *count = 0;
988
+ return nullptr;
989
+ }
990
+
991
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
992
+ auto peers = wrap->client->get_topic_peers(std::string(topic));
993
+
994
+ *count = static_cast<int>(peers.size());
995
+ if (*count == 0) return nullptr;
996
+
997
+ char** peer_array = static_cast<char**>(malloc(*count * sizeof(char*)));
998
+ if (!peer_array) {
999
+ *count = 0;
1000
+ return nullptr;
1001
+ }
1002
+
1003
+ for (int i = 0; i < *count; ++i) {
1004
+ peer_array[i] = rats_strdup_owned(peers[i]);
1005
+ if (!peer_array[i]) {
1006
+ // Clean up on allocation failure
1007
+ for (int j = 0; j < i; ++j) {
1008
+ free(peer_array[j]);
1009
+ }
1010
+ free(peer_array);
1011
+ *count = 0;
1012
+ return nullptr;
1013
+ }
1014
+ }
1015
+
1016
+ return peer_array;
1017
+ }
1018
+
1019
+ char** rats_get_topic_mesh_peers(rats_client_t handle, const char* topic, int* count) {
1020
+ if (!handle || !topic || !count) {
1021
+ if (count) *count = 0;
1022
+ return nullptr;
1023
+ }
1024
+
1025
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1026
+ auto peers = wrap->client->get_topic_mesh_peers(std::string(topic));
1027
+
1028
+ *count = static_cast<int>(peers.size());
1029
+ if (*count == 0) return nullptr;
1030
+
1031
+ char** peer_array = static_cast<char**>(malloc(*count * sizeof(char*)));
1032
+ if (!peer_array) {
1033
+ *count = 0;
1034
+ return nullptr;
1035
+ }
1036
+
1037
+ for (int i = 0; i < *count; ++i) {
1038
+ peer_array[i] = rats_strdup_owned(peers[i]);
1039
+ if (!peer_array[i]) {
1040
+ // Clean up on allocation failure
1041
+ for (int j = 0; j < i; ++j) {
1042
+ free(peer_array[j]);
1043
+ }
1044
+ free(peer_array);
1045
+ *count = 0;
1046
+ return nullptr;
1047
+ }
1048
+ }
1049
+
1050
+ return peer_array;
1051
+ }
1052
+
1053
+ char* rats_get_gossipsub_statistics_json(rats_client_t handle) {
1054
+ if (!handle) return nullptr;
1055
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1056
+ auto stats = wrap->client->get_gossipsub_statistics();
1057
+ return rats_strdup_owned(stats.dump());
1058
+ }
1059
+
1060
+ // GossipSub callback setters
1061
+ void rats_set_topic_message_callback(rats_client_t handle, const char* topic, rats_topic_message_cb cb, void* user_data) {
1062
+ if (!handle || !topic) return;
1063
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1064
+
1065
+ std::string topic_str(topic);
1066
+ if (cb) {
1067
+ wrap->topic_message_handlers[topic_str] = std::make_pair(cb, user_data);
1068
+ wrap->client->on_topic_message(topic_str, [wrap, cb, user_data](const std::string& peer_id, const std::string& topic, const std::string& message) {
1069
+ if (cb) {
1070
+ cb(user_data, peer_id.c_str(), topic.c_str(), message.c_str());
1071
+ }
1072
+ });
1073
+ } else {
1074
+ wrap->topic_message_handlers.erase(topic_str);
1075
+ }
1076
+ }
1077
+
1078
+ void rats_set_topic_json_message_callback(rats_client_t handle, const char* topic, rats_topic_json_message_cb cb, void* user_data) {
1079
+ if (!handle || !topic) return;
1080
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1081
+
1082
+ std::string topic_str(topic);
1083
+ if (cb) {
1084
+ wrap->topic_json_message_handlers[topic_str] = std::make_pair(cb, user_data);
1085
+ wrap->client->on_topic_json_message(topic_str, [wrap, cb, user_data](const std::string& peer_id, const std::string& topic, const nlohmann::json& message) {
1086
+ if (cb) {
1087
+ std::string json_str = message.dump();
1088
+ cb(user_data, peer_id.c_str(), topic.c_str(), json_str.c_str());
1089
+ }
1090
+ });
1091
+ } else {
1092
+ wrap->topic_json_message_handlers.erase(topic_str);
1093
+ }
1094
+ }
1095
+
1096
+ void rats_set_topic_peer_joined_callback(rats_client_t handle, const char* topic, rats_topic_peer_joined_cb cb, void* user_data) {
1097
+ if (!handle || !topic) return;
1098
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1099
+
1100
+ std::string topic_str(topic);
1101
+ if (cb) {
1102
+ wrap->topic_peer_joined_handlers[topic_str] = std::make_pair(cb, user_data);
1103
+ wrap->client->on_topic_peer_joined(topic_str, [wrap, cb, user_data](const std::string& peer_id, const std::string& topic) {
1104
+ if (cb) {
1105
+ cb(user_data, peer_id.c_str(), topic.c_str());
1106
+ }
1107
+ });
1108
+ } else {
1109
+ wrap->topic_peer_joined_handlers.erase(topic_str);
1110
+ }
1111
+ }
1112
+
1113
+ void rats_set_topic_peer_left_callback(rats_client_t handle, const char* topic, rats_topic_peer_left_cb cb, void* user_data) {
1114
+ if (!handle || !topic) return;
1115
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1116
+
1117
+ std::string topic_str(topic);
1118
+ if (cb) {
1119
+ wrap->topic_peer_left_handlers[topic_str] = std::make_pair(cb, user_data);
1120
+ wrap->client->on_topic_peer_left(topic_str, [wrap, cb, user_data](const std::string& peer_id, const std::string& topic) {
1121
+ if (cb) {
1122
+ cb(user_data, peer_id.c_str(), topic.c_str());
1123
+ }
1124
+ });
1125
+ } else {
1126
+ wrap->topic_peer_left_handlers.erase(topic_str);
1127
+ }
1128
+ }
1129
+
1130
+ void rats_clear_topic_callbacks(rats_client_t handle, const char* topic) {
1131
+ if (!handle || !topic) return;
1132
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1133
+
1134
+ std::string topic_str(topic);
1135
+ wrap->topic_message_handlers.erase(topic_str);
1136
+ wrap->topic_json_message_handlers.erase(topic_str);
1137
+ wrap->topic_peer_joined_handlers.erase(topic_str);
1138
+ wrap->topic_peer_left_handlers.erase(topic_str);
1139
+
1140
+ wrap->client->off_topic(topic_str);
1141
+ }
1142
+
1143
+ // ===================== NAT TRAVERSAL AND STUN =====================
1144
+
1145
+ rats_error_t rats_discover_and_ignore_public_ip(rats_client_t handle, const char* stun_server, int stun_port) {
1146
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
1147
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1148
+
1149
+ std::string server = stun_server ? std::string(stun_server) : "stun.l.google.com";
1150
+ int port = (stun_port > 0) ? stun_port : 19302;
1151
+
1152
+ return wrap->client->discover_and_ignore_public_ip(server, port) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
1153
+ }
1154
+
1155
+ char* rats_get_public_ip(rats_client_t handle) {
1156
+ if (!handle) return nullptr;
1157
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1158
+ std::string public_ip = wrap->client->get_public_ip();
1159
+ return public_ip.empty() ? nullptr : rats_strdup_owned(public_ip);
1160
+ }
1161
+
1162
+ int rats_detect_nat_type(rats_client_t handle) {
1163
+ if (!handle) return 0; // UNKNOWN
1164
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1165
+ return static_cast<int>(wrap->client->detect_nat_type());
1166
+ }
1167
+
1168
+ char* rats_get_nat_characteristics_json(rats_client_t handle) {
1169
+ if (!handle) return nullptr;
1170
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1171
+ auto characteristics = wrap->client->get_nat_characteristics();
1172
+
1173
+ // Convert NatTypeInfo to JSON manually
1174
+ nlohmann::json nat_json;
1175
+ nat_json["has_nat"] = characteristics.has_nat;
1176
+ nat_json["filtering_behavior"] = static_cast<int>(characteristics.filtering_behavior);
1177
+ nat_json["mapping_behavior"] = static_cast<int>(characteristics.mapping_behavior);
1178
+ nat_json["preserves_port"] = characteristics.preserves_port;
1179
+ nat_json["hairpin_support"] = characteristics.hairpin_support;
1180
+ nat_json["description"] = characteristics.description;
1181
+
1182
+ return rats_strdup_owned(nat_json.dump());
1183
+ }
1184
+
1185
+ void rats_add_ignored_address(rats_client_t handle, const char* ip_address) {
1186
+ if (!handle || !ip_address) return;
1187
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1188
+ wrap->client->add_ignored_address(std::string(ip_address));
1189
+ }
1190
+
1191
+ char* rats_get_nat_traversal_statistics_json(rats_client_t handle) {
1192
+ if (!handle) return nullptr;
1193
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1194
+ auto stats = wrap->client->get_nat_traversal_statistics();
1195
+ return rats_strdup_owned(stats.dump());
1196
+ }
1197
+
1198
+ // ===================== ICE COORDINATION =====================
1199
+
1200
+ char* rats_create_ice_offer(rats_client_t handle, const char* peer_id) {
1201
+ if (!handle || !peer_id) return nullptr;
1202
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1203
+ auto offer = wrap->client->create_ice_offer(std::string(peer_id));
1204
+ return rats_strdup_owned(offer.dump());
1205
+ }
1206
+
1207
+ rats_error_t rats_connect_with_ice(rats_client_t handle, const char* peer_id, const char* ice_offer_json) {
1208
+ if (!handle || !peer_id || !ice_offer_json) return RATS_ERROR_INVALID_PARAMETER;
1209
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1210
+
1211
+ try {
1212
+ nlohmann::json offer = nlohmann::json::parse(ice_offer_json);
1213
+ return wrap->client->connect_with_ice(std::string(peer_id), offer) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
1214
+ } catch (const nlohmann::json::exception&) {
1215
+ return RATS_ERROR_JSON_PARSE;
1216
+ }
1217
+ }
1218
+
1219
+ rats_error_t rats_handle_ice_answer(rats_client_t handle, const char* peer_id, const char* ice_answer_json) {
1220
+ if (!handle || !peer_id || !ice_answer_json) return RATS_ERROR_INVALID_PARAMETER;
1221
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1222
+
1223
+ try {
1224
+ nlohmann::json answer = nlohmann::json::parse(ice_answer_json);
1225
+ return wrap->client->handle_ice_answer(std::string(peer_id), answer) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
1226
+ } catch (const nlohmann::json::exception&) {
1227
+ return RATS_ERROR_JSON_PARSE;
1228
+ }
1229
+ }
1230
+
1231
+ // ===================== CONFIGURATION PERSISTENCE =====================
1232
+
1233
+ rats_error_t rats_load_configuration(rats_client_t handle) {
1234
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
1235
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1236
+ return wrap->client->load_configuration() ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
1237
+ }
1238
+
1239
+ rats_error_t rats_save_configuration(rats_client_t handle) {
1240
+ if (!handle) return RATS_ERROR_INVALID_HANDLE;
1241
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1242
+ return wrap->client->save_configuration() ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
1243
+ }
1244
+
1245
+ rats_error_t rats_set_data_directory(rats_client_t handle, const char* directory_path) {
1246
+ if (!handle || !directory_path) return RATS_ERROR_INVALID_PARAMETER;
1247
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1248
+ return wrap->client->set_data_directory(std::string(directory_path)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
1249
+ }
1250
+
1251
+ char* rats_get_data_directory(rats_client_t handle) {
1252
+ if (!handle) return nullptr;
1253
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1254
+ return rats_strdup_owned(wrap->client->get_data_directory());
1255
+ }
1256
+
1257
+ int rats_load_and_reconnect_peers(rats_client_t handle) {
1258
+ if (!handle) return 0;
1259
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1260
+ return wrap->client->load_and_reconnect_peers();
1261
+ }
1262
+
1263
+ int rats_load_historical_peers(rats_client_t handle) {
1264
+ if (!handle) return 0;
1265
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1266
+ return wrap->client->load_historical_peers() ? 1 : 0;
1267
+ }
1268
+
1269
+ int rats_save_historical_peers(rats_client_t handle) {
1270
+ if (!handle) return 0;
1271
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1272
+ return wrap->client->save_historical_peers() ? 1 : 0;
1273
+ }
1274
+
1275
+ void rats_clear_historical_peers(rats_client_t handle) {
1276
+ if (!handle) return;
1277
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1278
+ wrap->client->clear_historical_peers();
1279
+ }
1280
+
1281
+ char** rats_get_historical_peer_ids(rats_client_t handle, int* count) {
1282
+ if (!handle || !count) {
1283
+ if (count) *count = 0;
1284
+ return nullptr;
1285
+ }
1286
+
1287
+ rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
1288
+ auto historical_peers = wrap->client->get_historical_peers();
1289
+
1290
+ *count = static_cast<int>(historical_peers.size());
1291
+ if (*count == 0) return nullptr;
1292
+
1293
+ char** peer_ids = static_cast<char**>(malloc(*count * sizeof(char*)));
1294
+ if (!peer_ids) {
1295
+ *count = 0;
1296
+ return nullptr;
1297
+ }
1298
+
1299
+ for (int i = 0; i < *count; ++i) {
1300
+ peer_ids[i] = rats_strdup_owned(historical_peers[i].peer_id);
1301
+ if (!peer_ids[i]) {
1302
+ // Clean up on allocation failure
1303
+ for (int j = 0; j < i; ++j) {
1304
+ free(peer_ids[j]);
1305
+ }
1306
+ free(peer_ids);
1307
+ *count = 0;
1308
+ return nullptr;
1309
+ }
1310
+ }
1311
+
1312
+ return peer_ids;
1313
+ }
1314
+
1315
+ } // extern "C"
1316
+
1317
+