Skip to main content

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}