rustp2p-c-ffi 0.1.0

C FFI bindings for rustp2p
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
use std::ffi::{CStr, CString};
use std::net::Ipv4Addr;
use std::os::raw::{c_char, c_int, c_ushort};
use std::ptr;
use std::str::FromStr;
use std::sync::Arc;

use rustp2p::cipher::Algorithm;
use rustp2p::node_id::GroupCode;
use rustp2p::{Builder, EndPoint, PeerNodeAddress, RecvMetadata, RecvUserData};
use tokio::runtime::Runtime;

// Opaque handle types for C
pub struct Rustp2pBuilder {
    builder: Builder,
    runtime: Arc<Runtime>,
    peers: Vec<PeerNodeAddress>,
}

pub struct Rustp2pEndpoint {
    endpoint: Arc<EndPoint>,
    runtime: Arc<Runtime>,
}

pub struct Rustp2pRecvData {
    data: RecvUserData,
    metadata: RecvMetadata,
}

// Error codes
pub const RUSTP2P_OK: c_int = 0;
pub const RUSTP2P_ERROR: c_int = -1;
pub const RUSTP2P_ERROR_NULL_PTR: c_int = -2;
pub const RUSTP2P_ERROR_INVALID_STR: c_int = -3;
pub const RUSTP2P_ERROR_INVALID_IP: c_int = -4;
pub const RUSTP2P_ERROR_BUILD_FAILED: c_int = -5;
pub const RUSTP2P_ERROR_WOULD_BLOCK: c_int = -6;
pub const RUSTP2P_ERROR_EOF: c_int = -7;

/// Create a new builder
/// Returns NULL on failure
#[no_mangle]
pub extern "C" fn rustp2p_builder_new() -> *mut Rustp2pBuilder {
    let runtime = match Runtime::new() {
        Ok(rt) => Arc::new(rt),
        Err(_) => return ptr::null_mut(),
    };

    let builder = Builder::new();
    let rust_builder = Rustp2pBuilder {
        builder,
        runtime,
        peers: Vec::new(),
    };
    Box::into_raw(Box::new(rust_builder))
}

/// Set UDP port
#[no_mangle]
pub extern "C" fn rustp2p_builder_udp_port(builder: *mut Rustp2pBuilder, port: c_ushort) -> c_int {
    if builder.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }
    let builder = unsafe { &mut *builder };
    builder.builder = std::mem::take(&mut builder.builder).udp_port(port);
    RUSTP2P_OK
}

/// Set TCP port
#[no_mangle]
pub extern "C" fn rustp2p_builder_tcp_port(builder: *mut Rustp2pBuilder, port: c_ushort) -> c_int {
    if builder.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }
    let builder = unsafe { &mut *builder };
    builder.builder = std::mem::take(&mut builder.builder).tcp_port(port);
    RUSTP2P_OK
}

/// Set node ID from IPv4 address string (e.g., "10.0.0.1")
#[no_mangle]
pub extern "C" fn rustp2p_builder_node_id(
    builder: *mut Rustp2pBuilder,
    node_id_str: *const c_char,
) -> c_int {
    if builder.is_null() || node_id_str.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let c_str = unsafe { CStr::from_ptr(node_id_str) };
    let ip_str = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => return RUSTP2P_ERROR_INVALID_STR,
    };

    let ipv4 = match Ipv4Addr::from_str(ip_str) {
        Ok(ip) => ip,
        Err(_) => return RUSTP2P_ERROR_INVALID_IP,
    };

    let builder = unsafe { &mut *builder };
    builder.builder = std::mem::take(&mut builder.builder).node_id(ipv4.into());
    RUSTP2P_OK
}

/// Set group code from string (e.g., "mygroup" or "12345")
/// The string will be converted to a 16-byte array (padded with zeros if shorter)
#[no_mangle]
pub extern "C" fn rustp2p_builder_group_code(
    builder: *mut Rustp2pBuilder,
    group_code: *const c_char,
) -> c_int {
    if builder.is_null() || group_code.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let c_str = unsafe { CStr::from_ptr(group_code) };
    let code_str = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => return RUSTP2P_ERROR_INVALID_STR,
    };

    let group_code = match GroupCode::try_from(code_str) {
        Ok(gc) => gc,
        Err(_) => return RUSTP2P_ERROR,
    };

    let builder = unsafe { &mut *builder };
    builder.builder = std::mem::take(&mut builder.builder).group_code(group_code);
    RUSTP2P_OK
}

/// Add a peer address (e.g., "udp://127.0.0.1:9090" or "tcp://192.168.1.1:8080")
#[no_mangle]
pub extern "C" fn rustp2p_builder_add_peer(
    builder: *mut Rustp2pBuilder,
    peer_addr: *const c_char,
) -> c_int {
    if builder.is_null() || peer_addr.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let c_str = unsafe { CStr::from_ptr(peer_addr) };
    let addr_str = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => return RUSTP2P_ERROR_INVALID_STR,
    };

    let peer = match PeerNodeAddress::from_str(addr_str) {
        Ok(p) => p,
        Err(_) => return RUSTP2P_ERROR,
    };

    let builder = unsafe { &mut *builder };
    builder.peers.push(peer);
    RUSTP2P_OK
}

/// Set encryption algorithm
/// algorithm: 0 = AesGcm, 1 = ChaCha20Poly1305 (if available)
/// password: the encryption password
#[no_mangle]
pub extern "C" fn rustp2p_builder_encryption(
    builder: *mut Rustp2pBuilder,
    algorithm: c_int,
    password: *const c_char,
) -> c_int {
    if builder.is_null() || password.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let c_str = unsafe { CStr::from_ptr(password) };
    let pwd = match c_str.to_str() {
        Ok(s) => s.to_string(),
        Err(_) => return RUSTP2P_ERROR_INVALID_STR,
    };

    // Try to create the algorithm based on what's available
    let algo = match algorithm {
        0 => {
            // AesGcm is available if either openssl or ring feature is enabled in rustp2p
            #[cfg(any(feature = "aes-gcm-openssl", feature = "aes-gcm-ring"))]
            {
                Algorithm::AesGcm(pwd)
            }
            #[cfg(not(any(feature = "aes-gcm-openssl", feature = "aes-gcm-ring")))]
            {
                let _ = pwd;
                return RUSTP2P_ERROR;
            }
        }
        1 => {
            // ChaCha20Poly1305 is available if either openssl or ring feature is enabled in rustp2p
            #[cfg(any(
                feature = "chacha20-poly1305-openssl",
                feature = "chacha20-poly1305-ring"
            ))]
            {
                Algorithm::ChaCha20Poly1305(pwd)
            }
            #[cfg(not(any(
                feature = "chacha20-poly1305-openssl",
                feature = "chacha20-poly1305-ring"
            )))]
            {
                let _ = pwd;
                return RUSTP2P_ERROR;
            }
        }
        _ => return RUSTP2P_ERROR,
    };

    let builder = unsafe { &mut *builder };
    builder.builder = std::mem::take(&mut builder.builder).encryption(algo);
    RUSTP2P_OK
}

/// Build the endpoint
/// Returns NULL on failure
#[no_mangle]
pub extern "C" fn rustp2p_builder_build(builder: *mut Rustp2pBuilder) -> *mut Rustp2pEndpoint {
    if builder.is_null() {
        return ptr::null_mut();
    }

    let builder = unsafe { Box::from_raw(builder) };
    let runtime = builder.runtime.clone();
    let mut builder_inner = builder.builder;

    // Add peers if any were configured
    if !builder.peers.is_empty() {
        builder_inner = builder_inner.peers(builder.peers);
    }

    let endpoint = match runtime.block_on(builder_inner.build()) {
        Ok(ep) => Arc::new(ep),
        Err(_) => return ptr::null_mut(),
    };

    let rust_endpoint = Rustp2pEndpoint { endpoint, runtime };
    Box::into_raw(Box::new(rust_endpoint))
}

/// Free/destroy the builder
#[no_mangle]
pub extern "C" fn rustp2p_builder_free(builder: *mut Rustp2pBuilder) {
    if !builder.is_null() {
        unsafe {
            let _ = Box::from_raw(builder);
        }
    }
}

/// Send data to a peer
/// dest_ip: destination node IP address string (e.g., "10.0.0.2")
/// data: pointer to data buffer
/// len: length of data
#[no_mangle]
pub extern "C" fn rustp2p_endpoint_send_to(
    endpoint: *mut Rustp2pEndpoint,
    dest_ip: *const c_char,
    data: *const u8,
    len: usize,
) -> c_int {
    if endpoint.is_null() || dest_ip.is_null() || data.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let endpoint = unsafe { &*endpoint };
    let c_str = unsafe { CStr::from_ptr(dest_ip) };
    let ip_str = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => return RUSTP2P_ERROR_INVALID_STR,
    };

    let ipv4 = match Ipv4Addr::from_str(ip_str) {
        Ok(ip) => ip,
        Err(_) => return RUSTP2P_ERROR_INVALID_IP,
    };

    let buf = unsafe { std::slice::from_raw_parts(data, len) };

    match endpoint
        .runtime
        .block_on(endpoint.endpoint.send_to(buf, ipv4))
    {
        Ok(_) => RUSTP2P_OK,
        Err(_) => RUSTP2P_ERROR,
    }
}

/// Try to send data to a peer (non-blocking)
#[no_mangle]
pub extern "C" fn rustp2p_endpoint_try_send_to(
    endpoint: *mut Rustp2pEndpoint,
    dest_ip: *const c_char,
    data: *const u8,
    len: usize,
) -> c_int {
    if endpoint.is_null() || dest_ip.is_null() || data.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let endpoint = unsafe { &*endpoint };
    let c_str = unsafe { CStr::from_ptr(dest_ip) };
    let ip_str = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => return RUSTP2P_ERROR_INVALID_STR,
    };

    let ipv4 = match Ipv4Addr::from_str(ip_str) {
        Ok(ip) => ip,
        Err(_) => return RUSTP2P_ERROR_INVALID_IP,
    };

    let buf = unsafe { std::slice::from_raw_parts(data, len) };

    match endpoint.endpoint.try_send_to(buf, ipv4) {
        Ok(_) => RUSTP2P_OK,
        Err(e) => {
            if e.kind() == std::io::ErrorKind::WouldBlock {
                RUSTP2P_ERROR_WOULD_BLOCK
            } else {
                RUSTP2P_ERROR
            }
        }
    }
}

/// Receive data from peers (blocking)
/// Returns pointer to received data structure, or NULL on error
/// Caller must free the returned pointer with rustp2p_recv_data_free
#[no_mangle]
pub extern "C" fn rustp2p_endpoint_recv_from(
    endpoint: *mut Rustp2pEndpoint,
) -> *mut Rustp2pRecvData {
    if endpoint.is_null() {
        return ptr::null_mut();
    }

    let endpoint = unsafe { &*endpoint };

    match endpoint.runtime.block_on(endpoint.endpoint.recv_from()) {
        Ok((data, metadata)) => {
            let recv_data = Rustp2pRecvData { data, metadata };
            Box::into_raw(Box::new(recv_data))
        }
        Err(_) => ptr::null_mut(),
    }
}

/// Try to receive data from peers (non-blocking)
/// Returns pointer to received data structure, or NULL if no data available or on error
/// Caller must free the returned pointer with rustp2p_recv_data_free
#[no_mangle]
pub extern "C" fn rustp2p_endpoint_try_recv_from(
    endpoint: *mut Rustp2pEndpoint,
) -> *mut Rustp2pRecvData {
    if endpoint.is_null() {
        return ptr::null_mut();
    }

    let endpoint = unsafe { &*endpoint };

    match endpoint.endpoint.try_recv_from() {
        Ok((data, metadata)) => {
            let recv_data = Rustp2pRecvData { data, metadata };
            Box::into_raw(Box::new(recv_data))
        }
        Err(_) => ptr::null_mut(),
    }
}

/// Get payload data from received data
/// out_data: output pointer to data (borrowed, don't free)
/// out_len: output length
#[no_mangle]
pub extern "C" fn rustp2p_recv_data_get_payload(
    recv_data: *const Rustp2pRecvData,
    out_data: *mut *const u8,
    out_len: *mut usize,
) -> c_int {
    if recv_data.is_null() || out_data.is_null() || out_len.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let recv_data = unsafe { &*recv_data };
    let payload = recv_data.data.payload();
    unsafe {
        *out_data = payload.as_ptr();
        *out_len = payload.len();
    }
    RUSTP2P_OK
}

/// Get source node ID from received data as IPv4 string
/// buffer: output buffer for IP string
/// buffer_len: size of buffer (should be at least 16 bytes for "xxx.xxx.xxx.xxx\0")
#[no_mangle]
pub extern "C" fn rustp2p_recv_data_get_src_id(
    recv_data: *const Rustp2pRecvData,
    buffer: *mut c_char,
    buffer_len: usize,
) -> c_int {
    if recv_data.is_null() || buffer.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let recv_data = unsafe { &*recv_data };
    let src_id: Ipv4Addr = recv_data.metadata.src_id().into();
    let ip_str = src_id.to_string();

    let c_string = match CString::new(ip_str) {
        Ok(s) => s,
        Err(_) => return RUSTP2P_ERROR,
    };

    let bytes = c_string.as_bytes_with_nul();
    if bytes.len() > buffer_len {
        return RUSTP2P_ERROR;
    }

    unsafe {
        ptr::copy_nonoverlapping(bytes.as_ptr() as *const c_char, buffer, bytes.len());
    }
    RUSTP2P_OK
}

/// Get destination node ID from received data as IPv4 string
#[no_mangle]
pub extern "C" fn rustp2p_recv_data_get_dest_id(
    recv_data: *const Rustp2pRecvData,
    buffer: *mut c_char,
    buffer_len: usize,
) -> c_int {
    if recv_data.is_null() || buffer.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let recv_data = unsafe { &*recv_data };
    let dest_id: Ipv4Addr = recv_data.metadata.dest_id().into();
    let ip_str = dest_id.to_string();

    let c_string = match CString::new(ip_str) {
        Ok(s) => s,
        Err(_) => return RUSTP2P_ERROR,
    };

    let bytes = c_string.as_bytes_with_nul();
    if bytes.len() > buffer_len {
        return RUSTP2P_ERROR;
    }

    unsafe {
        ptr::copy_nonoverlapping(bytes.as_ptr() as *const c_char, buffer, bytes.len());
    }
    RUSTP2P_OK
}

/// Check if the received data was relayed
#[no_mangle]
pub extern "C" fn rustp2p_recv_data_is_relay(recv_data: *const Rustp2pRecvData) -> c_int {
    if recv_data.is_null() {
        return RUSTP2P_ERROR_NULL_PTR;
    }

    let recv_data = unsafe { &*recv_data };
    if recv_data.metadata.is_relay() {
        1
    } else {
        0
    }
}

/// Free received data
#[no_mangle]
pub extern "C" fn rustp2p_recv_data_free(recv_data: *mut Rustp2pRecvData) {
    if !recv_data.is_null() {
        unsafe {
            let _ = Box::from_raw(recv_data);
        }
    }
}

/// Free/destroy the endpoint
#[no_mangle]
pub extern "C" fn rustp2p_endpoint_free(endpoint: *mut Rustp2pEndpoint) {
    if !endpoint.is_null() {
        unsafe {
            let _ = Box::from_raw(endpoint);
        }
    }
}