rustp2p_c_ffi/lib.rs
1//! # rustp2p-c-ffi - C FFI Bindings for rustp2p
2//!
3//! This crate provides C-compatible Foreign Function Interface (FFI) bindings for the `rustp2p`
4//! library, enabling integration with C, C++, and other languages that support C FFI.
5//!
6//! ## Features
7//!
8//! - **C-Compatible API**: Simple, pointer-based API for C/C++ integration
9//! - **Builder Pattern**: Fluent API for configuration
10//! - **Error Handling**: Clear error codes for all operations
11//! - **Memory Safety**: Proper ownership and cleanup functions
12//! - **Cross-Platform**: Works on Windows, Linux, and macOS
13//!
14//! ## Building
15//!
16//! This crate produces both a static library and a dynamic library:
17//!
18//! ```bash
19//! cargo build --release
20//! ```
21//!
22//! Output files:
23//! - `librustp2p_c_ffi.a` / `rustp2p_c_ffi.lib` (static)
24//! - `librustp2p_c_ffi.so` / `rustp2p_c_ffi.dll` / `librustp2p_c_ffi.dylib` (dynamic)
25//!
26//! ## C Usage Example
27//!
28//! ### Basic Setup
29//!
30//! ```c
31//! #include <stdio.h>
32//! #include <stdlib.h>
33//! #include <string.h>
34//!
35//! // Create and configure a node
36//! Rustp2pBuilder* builder = rustp2p_builder_new();
37//! rustp2p_builder_node_id(builder, "10.0.0.1");
38//! rustp2p_builder_udp_port(builder, 8080);
39//! rustp2p_builder_group_code(builder, "12345");
40//!
41//! // Build the endpoint
42//! Rustp2pEndpoint* endpoint = rustp2p_builder_build(builder);
43//! if (!endpoint) {
44//! fprintf(stderr, "Failed to build endpoint\n");
45//! return -1;
46//! }
47//! ```
48//!
49//! ### Sending Data
50//!
51//! ```c
52//! const char* message = "Hello, peer!";
53//! int result = rustp2p_endpoint_send_to(
54//! endpoint,
55//! "10.0.0.2",
56//! (const uint8_t*)message,
57//! strlen(message)
58//! );
59//!
60//! if (result == RUSTP2P_OK) {
61//! printf("Message sent successfully\n");
62//! }
63//! ```
64//!
65//! ### Receiving Data
66//!
67//! ```c
68//! // Blocking receive
69//! Rustp2pRecvData* recv_data = rustp2p_endpoint_recv_from(endpoint);
70//! if (recv_data) {
71//! const uint8_t* data;
72//! size_t len;
73//! rustp2p_recv_data_get_payload(recv_data, &data, &len);
74//!
75//! char src_id[16];
76//! rustp2p_recv_data_get_src_id(recv_data, src_id, sizeof(src_id));
77//!
78//! printf("Received %zu bytes from %s\n", len, src_id);
79//!
80//! rustp2p_recv_data_free(recv_data);
81//! }
82//! ```
83//!
84//! ### Cleanup
85//!
86//! ```c
87//! rustp2p_endpoint_free(endpoint);
88//! ```
89//!
90//! ## Error Codes
91//!
92//! The library uses the following error codes:
93//!
94//! - `RUSTP2P_OK` (0): Success
95//! - `RUSTP2P_ERROR` (-1): General error
96//! - `RUSTP2P_ERROR_NULL_PTR` (-2): NULL pointer provided
97//! - `RUSTP2P_ERROR_INVALID_STR` (-3): Invalid string
98//! - `RUSTP2P_ERROR_INVALID_IP` (-4): Invalid IP address
99//! - `RUSTP2P_ERROR_BUILD_FAILED` (-5): Failed to build endpoint
100//! - `RUSTP2P_ERROR_WOULD_BLOCK` (-6): Operation would block
101//! - `RUSTP2P_ERROR_EOF` (-7): End of file / connection closed
102//!
103//! ## Complete C Example
104//!
105//! ```c
106//! #include <stdio.h>
107//! #include <stdlib.h>
108//! #include <string.h>
109//! #include <pthread.h>
110//!
111//! void* receive_thread(void* arg) {
112//! Rustp2pEndpoint* endpoint = (Rustp2pEndpoint*)arg;
113//!
114//! while (1) {
115//! Rustp2pRecvData* recv_data = rustp2p_endpoint_recv_from(endpoint);
116//! if (!recv_data) {
117//! break;
118//! }
119//!
120//! const uint8_t* data;
121//! size_t len;
122//! rustp2p_recv_data_get_payload(recv_data, &data, &len);
123//!
124//! char src_id[16];
125//! rustp2p_recv_data_get_src_id(recv_data, src_id, sizeof(src_id));
126//!
127//! printf("Received %zu bytes from %s: %.*s\n", len, src_id, (int)len, data);
128//!
129//! rustp2p_recv_data_free(recv_data);
130//! }
131//!
132//! return NULL;
133//! }
134//!
135//! int main() {
136//! // Create builder
137//! Rustp2pBuilder* builder = rustp2p_builder_new();
138//! if (!builder) {
139//! return -1;
140//! }
141//!
142//! // Configure
143//! rustp2p_builder_node_id(builder, "10.0.0.1");
144//! rustp2p_builder_udp_port(builder, 8080);
145//! rustp2p_builder_tcp_port(builder, 8080);
146//! rustp2p_builder_group_code(builder, "12345");
147//! rustp2p_builder_add_peer(builder, "udp://192.168.1.100:9090");
148//!
149//! // Build endpoint
150//! Rustp2pEndpoint* endpoint = rustp2p_builder_build(builder);
151//! if (!endpoint) {
152//! fprintf(stderr, "Failed to build endpoint\n");
153//! return -1;
154//! }
155//!
156//! // Start receive thread
157//! pthread_t thread;
158//! pthread_create(&thread, NULL, receive_thread, endpoint);
159//!
160//! // Send messages
161//! const char* message = "Hello from C!";
162//! rustp2p_endpoint_send_to(endpoint, "10.0.0.2",
163//! (const uint8_t*)message, strlen(message));
164//!
165//! // Wait for thread
166//! pthread_join(thread, NULL);
167//!
168//! // Cleanup
169//! rustp2p_endpoint_free(endpoint);
170//!
171//! return 0;
172//! }
173//! ```
174//!
175//! ## C++ Integration
176//!
177//! For C++ projects, wrap the C API in RAII classes:
178//!
179//! ```cpp
180//! class Rustp2pEndpointWrapper {
181//! Rustp2pEndpoint* endpoint_;
182//! public:
183//! Rustp2pEndpointWrapper(Rustp2pEndpoint* ep) : endpoint_(ep) {}
184//! ~Rustp2pEndpointWrapper() {
185//! if (endpoint_) {
186//! rustp2p_endpoint_free(endpoint_);
187//! }
188//! }
189//!
190//! // Prevent copying
191//! Rustp2pEndpointWrapper(const Rustp2pEndpointWrapper&) = delete;
192//! Rustp2pEndpointWrapper& operator=(const Rustp2pEndpointWrapper&) = delete;
193//!
194//! Rustp2pEndpoint* get() { return endpoint_; }
195//! };
196//! ```
197//!
198//! ## JavaScript/TypeScript Integration
199//!
200//! See [JAVASCRIPT.md](https://github.com/rustp2p/rustp2p/blob/master/rustp2p-c-ffi/JAVASCRIPT.md)
201//! for details on using these bindings with Node.js via `node-ffi-napi`.
202//!
203//! ## Thread Safety
204//!
205//! - All functions are thread-safe
206//! - Multiple threads can call send/receive operations simultaneously
207//! - The Tokio runtime is managed internally
208//!
209//! ## Memory Management
210//!
211//! - Always call the corresponding `_free` function for allocated resources
212//! - Do not use pointers after calling `_free`
213//! - Received data pointers are valid only until `rustp2p_recv_data_free` is called
214//!
215//! ## See Also
216//!
217//! - [`rustp2p`](../rustp2p/index.html) - The main Rust library
218//! - [GitHub Repository](https://github.com/rustp2p/rustp2p)
219
220use std::ffi::{CStr, CString};
221use std::net::Ipv4Addr;
222use std::os::raw::{c_char, c_int, c_ushort};
223use std::ptr;
224use std::str::FromStr;
225use std::sync::Arc;
226
227use rustp2p::cipher::Algorithm;
228use rustp2p::node_id::GroupCode;
229use rustp2p::{Builder, EndPoint, PeerNodeAddress, RecvMetadata, RecvUserData};
230use tokio::runtime::Runtime;
231
232// Opaque handle types for C
233/// Builder handle for configuring a rustp2p endpoint.
234///
235/// Use the `rustp2p_builder_*` functions to configure, then call
236/// `rustp2p_builder_build` to create an endpoint.
237pub struct Rustp2pBuilder {
238 builder: Builder,
239 runtime: Arc<Runtime>,
240 peers: Vec<PeerNodeAddress>,
241}
242
243/// Endpoint handle for sending and receiving P2P data.
244///
245/// Create with `rustp2p_builder_build`, free with `rustp2p_endpoint_free`.
246pub struct Rustp2pEndpoint {
247 endpoint: Arc<EndPoint>,
248 runtime: Arc<Runtime>,
249}
250
251/// Received data handle containing both payload and metadata.
252///
253/// Use `rustp2p_recv_data_get_*` functions to extract information,
254/// then call `rustp2p_recv_data_free` to release.
255pub struct Rustp2pRecvData {
256 data: RecvUserData,
257 metadata: RecvMetadata,
258}
259
260// Error codes
261/// Operation completed successfully.
262pub const RUSTP2P_OK: c_int = 0;
263/// General error occurred.
264pub const RUSTP2P_ERROR: c_int = -1;
265/// NULL pointer was provided where a valid pointer was expected.
266pub const RUSTP2P_ERROR_NULL_PTR: c_int = -2;
267/// Invalid string format or encoding.
268pub const RUSTP2P_ERROR_INVALID_STR: c_int = -3;
269/// Invalid IP address format.
270pub const RUSTP2P_ERROR_INVALID_IP: c_int = -4;
271/// Failed to build endpoint (e.g., port in use, invalid config).
272pub const RUSTP2P_ERROR_BUILD_FAILED: c_int = -5;
273/// Operation would block (for non-blocking operations).
274pub const RUSTP2P_ERROR_WOULD_BLOCK: c_int = -6;
275/// End of file / connection closed.
276pub const RUSTP2P_ERROR_EOF: c_int = -7;
277
278/// Create a new builder
279///
280/// Returns a new builder handle that must be freed with `rustp2p_builder_free`
281/// or consumed by `rustp2p_builder_build`.
282///
283/// # Returns
284///
285/// Returns a valid builder pointer on success, or NULL on failure (e.g., out of memory).
286///
287/// # Example
288///
289/// ```c
290/// Rustp2pBuilder* builder = rustp2p_builder_new();
291/// if (!builder) {
292/// fprintf(stderr, "Failed to create builder\n");
293/// return -1;
294/// }
295/// ```
296#[no_mangle]
297pub extern "C" fn rustp2p_builder_new() -> *mut Rustp2pBuilder {
298 let runtime = match Runtime::new() {
299 Ok(rt) => Arc::new(rt),
300 Err(_) => return ptr::null_mut(),
301 };
302
303 let builder = Builder::new();
304 let rust_builder = Rustp2pBuilder {
305 builder,
306 runtime,
307 peers: Vec::new(),
308 };
309 Box::into_raw(Box::new(rust_builder))
310}
311
312/// Set UDP port
313///
314/// Configures the UDP port for this endpoint. Use 0 for a random port.
315///
316/// # Arguments
317///
318/// * `builder` - The builder handle
319/// * `port` - The UDP port number (0-65535)
320///
321/// # Returns
322///
323/// * `RUSTP2P_OK` on success
324/// * `RUSTP2P_ERROR_NULL_PTR` if builder is NULL
325///
326/// # Example
327///
328/// ```c
329/// rustp2p_builder_udp_port(builder, 8080);
330/// ```
331#[no_mangle]
332pub extern "C" fn rustp2p_builder_udp_port(builder: *mut Rustp2pBuilder, port: c_ushort) -> c_int {
333 if builder.is_null() {
334 return RUSTP2P_ERROR_NULL_PTR;
335 }
336 let builder = unsafe { &mut *builder };
337 builder.builder = std::mem::take(&mut builder.builder).udp_port(port);
338 RUSTP2P_OK
339}
340
341/// Set TCP port
342///
343/// Configures the TCP port for this endpoint. Use 0 for a random port.
344///
345/// # Arguments
346///
347/// * `builder` - The builder handle
348/// * `port` - The TCP port number (0-65535)
349///
350/// # Returns
351///
352/// * `RUSTP2P_OK` on success
353/// * `RUSTP2P_ERROR_NULL_PTR` if builder is NULL
354///
355/// # Example
356///
357/// ```c
358/// rustp2p_builder_tcp_port(builder, 8080);
359/// ```
360#[no_mangle]
361pub extern "C" fn rustp2p_builder_tcp_port(builder: *mut Rustp2pBuilder, port: c_ushort) -> c_int {
362 if builder.is_null() {
363 return RUSTP2P_ERROR_NULL_PTR;
364 }
365 let builder = unsafe { &mut *builder };
366 builder.builder = std::mem::take(&mut builder.builder).tcp_port(port);
367 RUSTP2P_OK
368}
369
370/// Set node ID from IPv4 address string (e.g., "10.0.0.1")
371///
372/// Each node in the P2P network must have a unique node ID.
373///
374/// # Arguments
375///
376/// * `builder` - The builder handle
377/// * `node_id_str` - NULL-terminated IPv4 address string
378///
379/// # Returns
380///
381/// * `RUSTP2P_OK` on success
382/// * `RUSTP2P_ERROR_NULL_PTR` if builder or node_id_str is NULL
383/// * `RUSTP2P_ERROR_INVALID_STR` if the string is not valid UTF-8
384/// * `RUSTP2P_ERROR_INVALID_IP` if the IP address format is invalid
385///
386/// # Example
387///
388/// ```c
389/// int result = rustp2p_builder_node_id(builder, "10.0.0.1");
390/// if (result != RUSTP2P_OK) {
391/// fprintf(stderr, "Failed to set node ID\n");
392/// }
393/// ```
394#[no_mangle]
395pub extern "C" fn rustp2p_builder_node_id(
396 builder: *mut Rustp2pBuilder,
397 node_id_str: *const c_char,
398) -> c_int {
399 if builder.is_null() || node_id_str.is_null() {
400 return RUSTP2P_ERROR_NULL_PTR;
401 }
402
403 let c_str = unsafe { CStr::from_ptr(node_id_str) };
404 let ip_str = match c_str.to_str() {
405 Ok(s) => s,
406 Err(_) => return RUSTP2P_ERROR_INVALID_STR,
407 };
408
409 let ipv4 = match Ipv4Addr::from_str(ip_str) {
410 Ok(ip) => ip,
411 Err(_) => return RUSTP2P_ERROR_INVALID_IP,
412 };
413
414 let builder = unsafe { &mut *builder };
415 builder.builder = std::mem::take(&mut builder.builder).node_id(ipv4.into());
416 RUSTP2P_OK
417}
418
419/// Set group code from string (e.g., "mygroup" or "12345")
420///
421/// The group code creates isolated P2P networks. Nodes with different
422/// group codes cannot communicate with each other.
423///
424/// The string will be converted to a 16-byte array (padded with zeros if shorter).
425///
426/// # Arguments
427///
428/// * `builder` - The builder handle
429/// * `group_code` - NULL-terminated group code string
430///
431/// # Returns
432///
433/// * `RUSTP2P_OK` on success
434/// * `RUSTP2P_ERROR_NULL_PTR` if builder or group_code is NULL
435/// * `RUSTP2P_ERROR_INVALID_STR` if the string is not valid UTF-8
436/// * `RUSTP2P_ERROR` if the group code format is invalid
437///
438/// # Example
439///
440/// ```c
441/// rustp2p_builder_group_code(builder, "12345");
442/// ```
443#[no_mangle]
444pub extern "C" fn rustp2p_builder_group_code(
445 builder: *mut Rustp2pBuilder,
446 group_code: *const c_char,
447) -> c_int {
448 if builder.is_null() || group_code.is_null() {
449 return RUSTP2P_ERROR_NULL_PTR;
450 }
451
452 let c_str = unsafe { CStr::from_ptr(group_code) };
453 let code_str = match c_str.to_str() {
454 Ok(s) => s,
455 Err(_) => return RUSTP2P_ERROR_INVALID_STR,
456 };
457
458 let group_code = match GroupCode::try_from(code_str) {
459 Ok(gc) => gc,
460 Err(_) => return RUSTP2P_ERROR,
461 };
462
463 let builder = unsafe { &mut *builder };
464 builder.builder = std::mem::take(&mut builder.builder).group_code(group_code);
465 RUSTP2P_OK
466}
467
468/// Add a peer address (e.g., "udp://127.0.0.1:9090" or "tcp://192.168.1.1:8080")
469///
470/// Adds an initial peer that this node will connect to. You can call this
471/// multiple times to add multiple peers.
472///
473/// # Arguments
474///
475/// * `builder` - The builder handle
476/// * `peer_addr` - NULL-terminated peer address string (format: "protocol://ip:port")
477///
478/// # Returns
479///
480/// * `RUSTP2P_OK` on success
481/// * `RUSTP2P_ERROR_NULL_PTR` if builder or peer_addr is NULL
482/// * `RUSTP2P_ERROR_INVALID_STR` if the string is not valid UTF-8
483/// * `RUSTP2P_ERROR` if the address format is invalid
484///
485/// # Example
486///
487/// ```c
488/// rustp2p_builder_add_peer(builder, "udp://192.168.1.100:9090");
489/// rustp2p_builder_add_peer(builder, "tcp://10.0.0.1:8080");
490/// ```
491#[no_mangle]
492pub extern "C" fn rustp2p_builder_add_peer(
493 builder: *mut Rustp2pBuilder,
494 peer_addr: *const c_char,
495) -> c_int {
496 if builder.is_null() || peer_addr.is_null() {
497 return RUSTP2P_ERROR_NULL_PTR;
498 }
499
500 let c_str = unsafe { CStr::from_ptr(peer_addr) };
501 let addr_str = match c_str.to_str() {
502 Ok(s) => s,
503 Err(_) => return RUSTP2P_ERROR_INVALID_STR,
504 };
505
506 let peer = match PeerNodeAddress::from_str(addr_str) {
507 Ok(p) => p,
508 Err(_) => return RUSTP2P_ERROR,
509 };
510
511 let builder = unsafe { &mut *builder };
512 builder.peers.push(peer);
513 RUSTP2P_OK
514}
515
516/// Set encryption algorithm
517/// algorithm: 0 = AesGcm, 1 = ChaCha20Poly1305 (if available)
518/// password: the encryption password
519#[no_mangle]
520pub extern "C" fn rustp2p_builder_encryption(
521 builder: *mut Rustp2pBuilder,
522 algorithm: c_int,
523 password: *const c_char,
524) -> c_int {
525 if builder.is_null() || password.is_null() {
526 return RUSTP2P_ERROR_NULL_PTR;
527 }
528
529 let c_str = unsafe { CStr::from_ptr(password) };
530 let pwd = match c_str.to_str() {
531 Ok(s) => s.to_string(),
532 Err(_) => return RUSTP2P_ERROR_INVALID_STR,
533 };
534
535 // Try to create the algorithm based on what's available
536 let algo = match algorithm {
537 0 => {
538 // AesGcm is available if either openssl or ring feature is enabled in rustp2p
539 #[cfg(any(feature = "aes-gcm-openssl", feature = "aes-gcm-ring"))]
540 {
541 Algorithm::AesGcm(pwd)
542 }
543 #[cfg(not(any(feature = "aes-gcm-openssl", feature = "aes-gcm-ring")))]
544 {
545 let _ = pwd;
546 return RUSTP2P_ERROR;
547 }
548 }
549 1 => {
550 // ChaCha20Poly1305 is available if either openssl or ring feature is enabled in rustp2p
551 #[cfg(any(
552 feature = "chacha20-poly1305-openssl",
553 feature = "chacha20-poly1305-ring"
554 ))]
555 {
556 Algorithm::ChaCha20Poly1305(pwd)
557 }
558 #[cfg(not(any(
559 feature = "chacha20-poly1305-openssl",
560 feature = "chacha20-poly1305-ring"
561 )))]
562 {
563 let _ = pwd;
564 return RUSTP2P_ERROR;
565 }
566 }
567 _ => return RUSTP2P_ERROR,
568 };
569
570 let builder = unsafe { &mut *builder };
571 builder.builder = std::mem::take(&mut builder.builder).encryption(algo);
572 RUSTP2P_OK
573}
574
575/// Build the endpoint
576///
577/// Consumes the builder and creates an endpoint. The builder is freed automatically
578/// and must not be used after this call, regardless of success or failure.
579///
580/// # Arguments
581///
582/// * `builder` - The builder handle (will be freed)
583///
584/// # Returns
585///
586/// Returns a valid endpoint pointer on success, or NULL on failure.
587/// Common failure reasons include:
588/// - Port already in use
589/// - Missing required configuration (e.g., node_id)
590/// - Network initialization errors
591///
592/// # Example
593///
594/// ```c
595/// Rustp2pEndpoint* endpoint = rustp2p_builder_build(builder);
596/// if (!endpoint) {
597/// fprintf(stderr, "Failed to build endpoint\n");
598/// return -1;
599/// }
600/// // Use endpoint...
601/// rustp2p_endpoint_free(endpoint);
602/// ```
603#[no_mangle]
604pub extern "C" fn rustp2p_builder_build(builder: *mut Rustp2pBuilder) -> *mut Rustp2pEndpoint {
605 if builder.is_null() {
606 return ptr::null_mut();
607 }
608
609 let builder = unsafe { Box::from_raw(builder) };
610 let runtime = builder.runtime.clone();
611 let mut builder_inner = builder.builder;
612
613 // Add peers if any were configured
614 if !builder.peers.is_empty() {
615 builder_inner = builder_inner.peers(builder.peers);
616 }
617
618 let endpoint = match runtime.block_on(builder_inner.build()) {
619 Ok(ep) => Arc::new(ep),
620 Err(_) => return ptr::null_mut(),
621 };
622
623 let rust_endpoint = Rustp2pEndpoint { endpoint, runtime };
624 Box::into_raw(Box::new(rust_endpoint))
625}
626
627/// Free/destroy the builder
628///
629/// Releases all resources associated with the builder. Only use this if you
630/// did NOT call `rustp2p_builder_build` (which frees the builder automatically).
631///
632/// # Safety
633///
634/// The builder pointer must not be used after this call.
635///
636/// # Arguments
637///
638/// * `builder` - The builder handle to free (can be NULL, in which case this is a no-op)
639#[no_mangle]
640pub extern "C" fn rustp2p_builder_free(builder: *mut Rustp2pBuilder) {
641 if !builder.is_null() {
642 unsafe {
643 let _ = Box::from_raw(builder);
644 }
645 }
646}
647
648/// Send data to a peer
649///
650/// Sends data to a specific peer by their node ID. This operation blocks until
651/// the data is queued for sending (but not necessarily delivered).
652///
653/// # Arguments
654///
655/// * `endpoint` - The endpoint handle
656/// * `dest_ip` - NULL-terminated destination node IP address string (e.g., "10.0.0.2")
657/// * `data` - Pointer to data buffer
658/// * `len` - Length of data in bytes
659///
660/// # Returns
661///
662/// * `RUSTP2P_OK` on success
663/// * `RUSTP2P_ERROR_NULL_PTR` if any pointer is NULL
664/// * `RUSTP2P_ERROR_INVALID_STR` if dest_ip is not valid UTF-8
665/// * `RUSTP2P_ERROR_INVALID_IP` if dest_ip format is invalid
666/// * `RUSTP2P_ERROR` on other errors (e.g., network failure)
667///
668/// # Example
669///
670/// ```c
671/// const char* message = "Hello, peer!";
672/// int result = rustp2p_endpoint_send_to(
673/// endpoint,
674/// "10.0.0.2",
675/// (const uint8_t*)message,
676/// strlen(message)
677/// );
678///
679/// if (result == RUSTP2P_OK) {
680/// printf("Message sent\n");
681/// }
682/// ```
683#[no_mangle]
684pub extern "C" fn rustp2p_endpoint_send_to(
685 endpoint: *mut Rustp2pEndpoint,
686 dest_ip: *const c_char,
687 data: *const u8,
688 len: usize,
689) -> c_int {
690 if endpoint.is_null() || dest_ip.is_null() || data.is_null() {
691 return RUSTP2P_ERROR_NULL_PTR;
692 }
693
694 let endpoint = unsafe { &*endpoint };
695 let c_str = unsafe { CStr::from_ptr(dest_ip) };
696 let ip_str = match c_str.to_str() {
697 Ok(s) => s,
698 Err(_) => return RUSTP2P_ERROR_INVALID_STR,
699 };
700
701 let ipv4 = match Ipv4Addr::from_str(ip_str) {
702 Ok(ip) => ip,
703 Err(_) => return RUSTP2P_ERROR_INVALID_IP,
704 };
705
706 let buf = unsafe { std::slice::from_raw_parts(data, len) };
707
708 match endpoint
709 .runtime
710 .block_on(endpoint.endpoint.send_to(buf, ipv4))
711 {
712 Ok(_) => RUSTP2P_OK,
713 Err(_) => RUSTP2P_ERROR,
714 }
715}
716
717/// Try to send data to a peer (non-blocking)
718#[no_mangle]
719pub extern "C" fn rustp2p_endpoint_try_send_to(
720 endpoint: *mut Rustp2pEndpoint,
721 dest_ip: *const c_char,
722 data: *const u8,
723 len: usize,
724) -> c_int {
725 if endpoint.is_null() || dest_ip.is_null() || data.is_null() {
726 return RUSTP2P_ERROR_NULL_PTR;
727 }
728
729 let endpoint = unsafe { &*endpoint };
730 let c_str = unsafe { CStr::from_ptr(dest_ip) };
731 let ip_str = match c_str.to_str() {
732 Ok(s) => s,
733 Err(_) => return RUSTP2P_ERROR_INVALID_STR,
734 };
735
736 let ipv4 = match Ipv4Addr::from_str(ip_str) {
737 Ok(ip) => ip,
738 Err(_) => return RUSTP2P_ERROR_INVALID_IP,
739 };
740
741 let buf = unsafe { std::slice::from_raw_parts(data, len) };
742
743 match endpoint.endpoint.try_send_to(buf, ipv4) {
744 Ok(_) => RUSTP2P_OK,
745 Err(e) => {
746 if e.kind() == std::io::ErrorKind::WouldBlock {
747 RUSTP2P_ERROR_WOULD_BLOCK
748 } else {
749 RUSTP2P_ERROR
750 }
751 }
752 }
753}
754
755/// Receive data from peers (blocking)
756///
757/// Blocks until data is received from any peer or an error occurs.
758/// The returned structure must be freed with `rustp2p_recv_data_free`.
759///
760/// # Arguments
761///
762/// * `endpoint` - The endpoint handle
763///
764/// # Returns
765///
766/// Returns pointer to received data structure, or NULL on error or connection closed.
767/// Caller must free the returned pointer with `rustp2p_recv_data_free`.
768///
769/// # Example
770///
771/// ```c
772/// Rustp2pRecvData* recv_data = rustp2p_endpoint_recv_from(endpoint);
773/// if (recv_data) {
774/// const uint8_t* data;
775/// size_t len;
776/// rustp2p_recv_data_get_payload(recv_data, &data, &len);
777/// printf("Received %zu bytes\n", len);
778/// rustp2p_recv_data_free(recv_data);
779/// }
780/// ```
781#[no_mangle]
782pub extern "C" fn rustp2p_endpoint_recv_from(
783 endpoint: *mut Rustp2pEndpoint,
784) -> *mut Rustp2pRecvData {
785 if endpoint.is_null() {
786 return ptr::null_mut();
787 }
788
789 let endpoint = unsafe { &*endpoint };
790
791 match endpoint.runtime.block_on(endpoint.endpoint.recv_from()) {
792 Ok((data, metadata)) => {
793 let recv_data = Rustp2pRecvData { data, metadata };
794 Box::into_raw(Box::new(recv_data))
795 }
796 Err(_) => ptr::null_mut(),
797 }
798}
799
800/// Try to receive data from peers (non-blocking)
801/// Returns pointer to received data structure, or NULL if no data available or on error
802/// Caller must free the returned pointer with rustp2p_recv_data_free
803#[no_mangle]
804pub extern "C" fn rustp2p_endpoint_try_recv_from(
805 endpoint: *mut Rustp2pEndpoint,
806) -> *mut Rustp2pRecvData {
807 if endpoint.is_null() {
808 return ptr::null_mut();
809 }
810
811 let endpoint = unsafe { &*endpoint };
812
813 match endpoint.endpoint.try_recv_from() {
814 Ok((data, metadata)) => {
815 let recv_data = Rustp2pRecvData { data, metadata };
816 Box::into_raw(Box::new(recv_data))
817 }
818 Err(_) => ptr::null_mut(),
819 }
820}
821
822/// Get payload data from received data
823/// out_data: output pointer to data (borrowed, don't free)
824/// out_len: output length
825#[no_mangle]
826pub extern "C" fn rustp2p_recv_data_get_payload(
827 recv_data: *const Rustp2pRecvData,
828 out_data: *mut *const u8,
829 out_len: *mut usize,
830) -> c_int {
831 if recv_data.is_null() || out_data.is_null() || out_len.is_null() {
832 return RUSTP2P_ERROR_NULL_PTR;
833 }
834
835 let recv_data = unsafe { &*recv_data };
836 let payload = recv_data.data.payload();
837 unsafe {
838 *out_data = payload.as_ptr();
839 *out_len = payload.len();
840 }
841 RUSTP2P_OK
842}
843
844/// Get source node ID from received data as IPv4 string
845/// buffer: output buffer for IP string
846/// buffer_len: size of buffer (should be at least 16 bytes for "xxx.xxx.xxx.xxx\0")
847#[no_mangle]
848pub extern "C" fn rustp2p_recv_data_get_src_id(
849 recv_data: *const Rustp2pRecvData,
850 buffer: *mut c_char,
851 buffer_len: usize,
852) -> c_int {
853 if recv_data.is_null() || buffer.is_null() {
854 return RUSTP2P_ERROR_NULL_PTR;
855 }
856
857 let recv_data = unsafe { &*recv_data };
858 let src_id: Ipv4Addr = recv_data.metadata.src_id().into();
859 let ip_str = src_id.to_string();
860
861 let c_string = match CString::new(ip_str) {
862 Ok(s) => s,
863 Err(_) => return RUSTP2P_ERROR,
864 };
865
866 let bytes = c_string.as_bytes_with_nul();
867 if bytes.len() > buffer_len {
868 return RUSTP2P_ERROR;
869 }
870
871 unsafe {
872 ptr::copy_nonoverlapping(bytes.as_ptr() as *const c_char, buffer, bytes.len());
873 }
874 RUSTP2P_OK
875}
876
877/// Get destination node ID from received data as IPv4 string
878#[no_mangle]
879pub extern "C" fn rustp2p_recv_data_get_dest_id(
880 recv_data: *const Rustp2pRecvData,
881 buffer: *mut c_char,
882 buffer_len: usize,
883) -> c_int {
884 if recv_data.is_null() || buffer.is_null() {
885 return RUSTP2P_ERROR_NULL_PTR;
886 }
887
888 let recv_data = unsafe { &*recv_data };
889 let dest_id: Ipv4Addr = recv_data.metadata.dest_id().into();
890 let ip_str = dest_id.to_string();
891
892 let c_string = match CString::new(ip_str) {
893 Ok(s) => s,
894 Err(_) => return RUSTP2P_ERROR,
895 };
896
897 let bytes = c_string.as_bytes_with_nul();
898 if bytes.len() > buffer_len {
899 return RUSTP2P_ERROR;
900 }
901
902 unsafe {
903 ptr::copy_nonoverlapping(bytes.as_ptr() as *const c_char, buffer, bytes.len());
904 }
905 RUSTP2P_OK
906}
907
908/// Check if the received data was relayed
909#[no_mangle]
910pub extern "C" fn rustp2p_recv_data_is_relay(recv_data: *const Rustp2pRecvData) -> c_int {
911 if recv_data.is_null() {
912 return RUSTP2P_ERROR_NULL_PTR;
913 }
914
915 let recv_data = unsafe { &*recv_data };
916 if recv_data.metadata.is_relay() {
917 1
918 } else {
919 0
920 }
921}
922
923/// Free received data
924#[no_mangle]
925pub extern "C" fn rustp2p_recv_data_free(recv_data: *mut Rustp2pRecvData) {
926 if !recv_data.is_null() {
927 unsafe {
928 let _ = Box::from_raw(recv_data);
929 }
930 }
931}
932
933/// Free/destroy the endpoint
934///
935/// Releases all resources associated with the endpoint and closes all connections.
936///
937/// # Safety
938///
939/// The endpoint pointer must not be used after this call.
940///
941/// # Arguments
942///
943/// * `endpoint` - The endpoint handle to free (can be NULL, in which case this is a no-op)
944///
945/// # Example
946///
947/// ```c
948/// rustp2p_endpoint_free(endpoint);
949/// endpoint = NULL; // Good practice to avoid use-after-free
950/// ```
951#[no_mangle]
952pub extern "C" fn rustp2p_endpoint_free(endpoint: *mut Rustp2pEndpoint) {
953 if !endpoint.is_null() {
954 unsafe {
955 let _ = Box::from_raw(endpoint);
956 }
957 }
958}