1
//! Exposed C APIs for arti-rpc-client-core.
2
//!
3
//! See top-level documentation in header file for C conventions that affect the safety of these functions.
4
//! (These include things like "all input pointers must be valid" and so on.)
5

            
6
pub mod err;
7
mod util;
8

            
9
use err::{ArtiRpcError, InvalidInput};
10
use std::ffi::{c_char, c_int, c_void};
11
use std::sync::Mutex;
12
use util::{
13
    OptOutPtrExt as _, OptOutValExt, OutBoxedPtr, OutSocketOwned, OutVal, ffi_body_raw,
14
    ffi_body_with_err,
15
};
16

            
17
#[cfg(not(windows))]
18
use std::os::fd::{AsRawFd, BorrowedFd};
19

            
20
#[cfg(windows)]
21
use std::os::windows::io::{AsRawSocket, BorrowedSocket};
22

            
23
use crate::{
24
    ObjectId, RpcConnBuilder, RpcPoll,
25
    conn::{AnyResponse, RequestHandle},
26
    util::Utf8CString,
27
};
28

            
29
/// A status code returned by an Arti RPC function.
30
///
31
/// On success, a function will return `ARTI_SUCCESS (0)`.
32
/// On failure, a function will return some other status code.
33
pub type ArtiRpcStatus = u32;
34

            
35
/// An open connection to Arti over an a RPC protocol.
36
///
37
/// This is a thread-safe type: you may safely use it from multiple threads at once.
38
///
39
/// Once you are no longer going to use this connection at all, you must free
40
/// it with [`arti_rpc_conn_free`]
41
pub type ArtiRpcConn = crate::RpcConn;
42

            
43
/// A builder object used to configure and construct
44
/// a connection to Arti over the RPC protocol.
45
///
46
/// This is a thread-safe type: you may safely use it from multiple threads at once.
47
///
48
/// Once you are done with this object, you must free it with [`arti_rpc_conn_builder_free`].
49
pub struct ArtiRpcConnBuilder(Mutex<RpcConnBuilder>);
50

            
51
/// An object used to poll a nonblocking RPC connection for responses.
52
///
53
/// `ArtiRpcPoll` is used to integrate an Arti RPC connection with a polling-based
54
/// event loop.  See [`arti_rpc_conn_builder_connect_polling`] for more information.
55
//
56
// Note: we add a mutex to ArtiRpcPoll because we do not trust the user to keep its
57
// use to a single thread.
58
pub struct ArtiRpcPoll(Mutex<RpcPoll>);
59

            
60
/// An owned string, returned by this library.
61
///
62
/// This string must be released with `arti_rpc_str_free`.
63
/// You can inspect it with `arti_rpc_str_get`, but you may not modify it.
64
/// The string is guaranteed to be UTF-8 and NUL-terminated.
65
pub type ArtiRpcStr = Utf8CString;
66

            
67
/// A handle to an in-progress RPC request.
68
///
69
/// This handle must eventually be freed with `arti_rpc_handle_free`.
70
///
71
/// You can wait for the next message with `arti_rpc_handle_wait`.
72
pub type ArtiRpcHandle = RequestHandle;
73

            
74
/// The type of a message returned by an RPC request.
75
pub type ArtiRpcResponseType = c_int;
76

            
77
/// The type of an entry prepended to a connect point search path.
78
pub type ArtiRpcBuilderEntryType = c_int;
79

            
80
/// The type of a data stream socket.
81
/// (This is always `int` on Unix-like platforms,
82
/// and SOCKET on Windows.)
83
//
84
// NOTE: We declare this as a separate type so that we can give it a default.
85
#[repr(transparent)]
86
pub struct ArtiRpcRawSocket(
87
    #[cfg(windows)] std::os::windows::raw::SOCKET,
88
    #[cfg(not(windows))] c_int,
89
);
90

            
91
impl Default for ArtiRpcRawSocket {
92
    fn default() -> Self {
93
        #[cfg(windows)]
94
        {
95
            Self(!0)
96
        }
97
        #[cfg(not(windows))]
98
        {
99
            Self(-1)
100
        }
101
    }
102
}
103
#[cfg(not(windows))]
104
impl<'a> From<BorrowedFd<'a>> for ArtiRpcRawSocket {
105
    fn from(value: BorrowedFd<'a>) -> Self {
106
        Self(value.as_raw_fd())
107
    }
108
}
109
#[cfg(windows)]
110
impl<'a> From<BorrowedSocket<'a>> for ArtiRpcRawSocket {
111
    fn from(value: BorrowedSocket<'a>) -> Self {
112
        Self(value.as_raw_socket())
113
    }
114
}
115

            
116
/// User-provided information used to implement [`crate::EventLoop`].
117
///
118
/// This type is crate-internal; in the API, we instead take its members as arguments.
119
///
120
/// See [`crate::EventLoop`] for semantics, and [`arti_rpc_conn_builder_connect_polling`]
121
/// for semantics.
122
struct UserEventLoop {
123
    /// A function to invoke with `callback_data_ptr` when the connection starts wanting to write.
124
    /// Returns 0 or an errno.
125
    start_writing_callback: unsafe extern "C" fn(*mut c_void) -> c_int,
126
    /// A function to invoke with `callback_data_ptr` when the connection stop wanting to write.
127
    /// Returns 0 or an errno.
128
    stop_writing_callback: unsafe extern "C" fn(*mut c_void) -> c_int,
129
    /// An argument to pass to one of the callbacks in this struct.
130
    callback_data_ptr: *mut c_void,
131
}
132

            
133
impl crate::EventLoop for UserEventLoop {
134
    fn stop_writing(&mut self) -> std::io::Result<()> {
135
        // SAFETY: the safety requirements for this function are documented in
136
        // `arti_rpc_conn_builder_connect_polling`.
137
        let r = unsafe { (self.stop_writing_callback)(self.callback_data_ptr) };
138
        if r == 0 {
139
            Ok(())
140
        } else {
141
            Err(std::io::Error::from_raw_os_error(r))
142
        }
143
    }
144

            
145
    fn start_writing(&mut self) -> std::io::Result<()> {
146
        // SAFETY: the safety requirements for this function are documented in
147
        // `arti_rpc_conn_builder_connect_polling`.
148
        let r = unsafe { (self.start_writing_callback)(self.callback_data_ptr) };
149
        if r == 0 {
150
            Ok(())
151
        } else {
152
            Err(std::io::Error::from_raw_os_error(r))
153
        }
154
    }
155
}
156

            
157
// SAFETY: We document the thread-safety requirements for UserEventLoop's members in
158
// `arti_rpc_conn_builder_connect_polling`.
159
unsafe impl Send for UserEventLoop {}
160
unsafe impl Sync for UserEventLoop {}
161

            
162
/// A user-provided tag used to distinguish responses for requests submitted to
163
/// [`arti_rpc_conn_submit()`].
164
///
165
/// This value is two pointers wide to facilitate using it to store
166
/// a function pointer and an argument, where appropriate.
167
#[derive(Clone, Copy, Default)]
168
#[repr(C)]
169
#[allow(missing_docs, clippy::exhaustive_structs)]
170
pub struct ArtiRpcUserTag {
171
    pub a: usize,
172
    pub b: usize,
173
}
174

            
175
impl From<crate::UserTag> for ArtiRpcUserTag {
176
    fn from(value: crate::UserTag) -> Self {
177
        Self {
178
            a: value.0,
179
            b: value.1,
180
        }
181
    }
182
}
183
impl From<ArtiRpcUserTag> for crate::UserTag {
184
    fn from(value: ArtiRpcUserTag) -> Self {
185
        Self(value.a, value.b)
186
    }
187
}
188

            
189
/// Try to create a new `ArtiRpcConnBuilder`, with default settings.
190
///
191
/// On success, return `ARTI_RPC_STATUS_SUCCESS` and set `*builder_out`
192
/// to a new `ArtiRpcConnBuilder`.
193
/// Otherwise return some other status code, set `*builder_out` to NULL, and set
194
/// `*error_out` (if provided) to a newly allocated error object.
195
///
196
/// # Ownership
197
///
198
/// The caller is responsible for making sure that `*builder_out` and `*error_out`,
199
/// if set, are eventually freed.
200
#[allow(clippy::missing_safety_doc)]
201
#[unsafe(no_mangle)]
202
pub unsafe extern "C" fn arti_rpc_conn_builder_new(
203
    builder_out: *mut *mut ArtiRpcConnBuilder,
204
    error_out: *mut *mut ArtiRpcError,
205
) -> ArtiRpcStatus {
206
    ffi_body_with_err!(
207
        {
208
            let builder_out: Option<OutBoxedPtr<ArtiRpcConnBuilder>> [out_ptr_opt];
209
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
210
        } in {
211
            let builder = ArtiRpcConnBuilder(Mutex::new(RpcConnBuilder::new()));
212
            builder_out.write_boxed_value_if_ptr_set(builder);
213
        }
214
    )
215
}
216

            
217
/// Release storage held by an `ArtiRpcConnBuilder`.
218
#[allow(clippy::missing_safety_doc)]
219
#[unsafe(no_mangle)]
220
pub unsafe extern "C" fn arti_rpc_conn_builder_free(builder: *mut ArtiRpcConnBuilder) {
221
    ffi_body_raw!(
222
        {
223
            let builder: Option<Box<ArtiRpcConnBuilder>> [in_ptr_consume_opt];
224
        } in {
225
            drop(builder);
226
            // Safety: return value is (); trivially safe.
227
            ()
228
        }
229
    );
230
}
231

            
232
/// Constant to denote a literal connect point.
233
///
234
/// This constant is passed to [`arti_rpc_conn_builder_prepend_entry`].
235
pub const ARTI_RPC_BUILDER_ENTRY_LITERAL_CONNECT_POINT: ArtiRpcBuilderEntryType = 1;
236

            
237
/// Constant to denote a path in which Arti configuration variables are expanded.
238
///
239
/// This constant is passed to [`arti_rpc_conn_builder_prepend_entry`].
240
pub const ARTI_RPC_BUILDER_ENTRY_EXPANDABLE_PATH: ArtiRpcBuilderEntryType = 2;
241

            
242
/// Constant to denote a literal path that is not expanded.
243
///
244
/// This constant is passed to [`arti_rpc_conn_builder_prepend_entry`].
245
pub const ARTI_RPC_BUILDER_ENTRY_LITERAL_PATH: ArtiRpcBuilderEntryType = 3;
246

            
247
/// Prepend a single entry to the connection point path in `builder`.
248
///
249
/// This entry will be considered before any entries in `${ARTI_RPC_CONNECT_PATH}`,
250
/// but after any entry in `${ARTI_RPC_CONNECT_PATH_OVERRIDE}`.
251
///
252
/// The interpretation will depend on the value of `entry_type`.
253
///
254
/// On success, return `ARTI_RPC_STATUS_SUCCESS`.
255
/// Otherwise return some other status code, and set
256
/// `*error_out` (if provided) to a newly allocated error object.
257
///
258
/// # Ownership
259
///
260
/// The caller is responsible for making sure that `*error_out`,
261
/// if set, is eventually freed.
262
#[allow(clippy::missing_safety_doc)]
263
#[unsafe(no_mangle)]
264
pub unsafe extern "C" fn arti_rpc_conn_builder_prepend_entry(
265
    builder: *const ArtiRpcConnBuilder,
266
    entry_type: ArtiRpcBuilderEntryType,
267
    entry: *const c_char,
268
    error_out: *mut *mut ArtiRpcError,
269
) -> ArtiRpcStatus {
270
    ffi_body_with_err!(
271
        {
272
            let builder: Option<&ArtiRpcConnBuilder> [in_ptr_opt];
273
            let entry: Option<&str> [in_str_opt];
274
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
275
        } in {
276
            let builder = builder.ok_or(InvalidInput::NullPointer)?;
277
            let entry = entry.ok_or(InvalidInput::NullPointer)?;
278
            let mut b = builder.0.lock().expect("Poisoned lock");
279
            match entry_type {
280
                ARTI_RPC_BUILDER_ENTRY_LITERAL_CONNECT_POINT =>
281
                    b.prepend_literal_entry(entry.to_string()),
282
                ARTI_RPC_BUILDER_ENTRY_EXPANDABLE_PATH =>
283
                    b.prepend_path(entry.into()),
284
                ARTI_RPC_BUILDER_ENTRY_LITERAL_PATH =>
285
                    b.prepend_literal_path(entry.into()),
286
                _ => return Err(InvalidInput::InvalidConstValue.into()),
287
            }
288
        }
289
    )
290
}
291

            
292
/// Instruct `builder` to prefer connection points that grant superuser permission.
293
///
294
/// If no such connect points are found,
295
/// and `required` is true,
296
/// then the connection will fail with an error.
297
/// On success, return `ARTI_RPC_STATUS_SUCCESS`.
298
/// Otherwise return some other status code, and set
299
/// `*error_out` (if provided) to a newly allocated error object.
300
///
301
/// # Ownership
302
///
303
/// The caller is responsible for making sure that `*error_out`,
304
/// if set, is eventually freed.
305
#[allow(clippy::missing_safety_doc)]
306
#[unsafe(no_mangle)]
307
pub unsafe extern "C" fn arti_rpc_conn_builder_prefer_superuser_permission(
308
    builder: *const ArtiRpcConnBuilder,
309
    required: c_int,
310
    error_out: *mut *mut ArtiRpcError,
311
) -> ArtiRpcStatus {
312
    ffi_body_with_err!(
313
        {
314
            let builder: Option<&ArtiRpcConnBuilder> [in_ptr_opt];
315
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
316
        } in {
317
            let builder = builder.ok_or(InvalidInput::NullPointer)?;
318
            let mut b = builder.0.lock().expect("Poisoned lock");
319
            b.prefer_superuser_permission(required != 0);
320
        }
321
    )
322
}
323

            
324
/// Use `builder` to open a new RPC connection to Arti.
325
///
326
/// On success, return `ARTI_RPC_STATUS_SUCCESS`,
327
/// and set `conn_out` to a new ArtiRpcConn.
328
/// Otherwise return some other status code, set *conn_out to NULL, and set
329
/// `*error_out` (if provided) to a newly allocated error object.
330
///
331
/// # Ownership
332
///
333
/// The caller is responsible for making sure that `*rpc_conn_out` and `*error_out`,
334
/// if set, are eventually freed.
335
#[allow(clippy::missing_safety_doc)]
336
#[unsafe(no_mangle)]
337
pub unsafe extern "C" fn arti_rpc_conn_builder_connect(
338
    builder: *const ArtiRpcConnBuilder,
339
    rpc_conn_out: *mut *mut ArtiRpcConn,
340
    error_out: *mut *mut ArtiRpcError,
341
) -> ArtiRpcStatus {
342
    ffi_body_with_err!(
343
        {
344
            let builder: Option<&ArtiRpcConnBuilder> [in_ptr_opt];
345
            let rpc_conn_out: Option<OutBoxedPtr<ArtiRpcConn>> [out_ptr_opt];
346
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
347
        } in {
348
            let builder = builder.ok_or(InvalidInput::NullPointer)?;
349
            let b = builder.0.lock().expect("Poisoned lock");
350
            let conn = b.connect()?;
351
            rpc_conn_out.write_boxed_value_if_ptr_set(conn);
352
        }
353
    )
354
}
355

            
356
/// Use `builder` to open a new RPC connection to Arti,
357
/// with support to integrate with an event-driven IO loop.
358
///
359
/// If you are not integrating with poll() or select()-style loop,
360
/// you do not need to use this function.
361
///
362
/// On success, return `ARTI_RPC_STATUS_SUCCESS`,
363
/// set `rpc_conn_out` to a new ArtiRpcConn,
364
/// and set `poll_out` to a new ArtiRpcPoll.
365
/// Otherwise return some other status code, set *conn_out and *poll_out to NULL,
366
/// and set `*error_out` (if provided) to a newly allocated error object.
367
///
368
/// # Callbacks, data, and requirements.
369
///
370
/// The user code must provide an event loop
371
/// that can monitor an underlying connection for readability and writability.
372
///
373
/// The RPC library provides the user code with an OS handle to monitor.
374
/// The user code should always monitor this handle for readability.
375
/// It should monitor the handle for writability whenever the connection
376
/// "wants to write".
377
/// Whenever one of these events occurs, the user code should invoke
378
/// `arti_rpc_poll_poll` until it indicates that it would block.
379
///
380
/// The `start_writing_callback` and `start_reading_callback` functions
381
/// must be provided.  They will be invoked (respectively) whenever the connection
382
/// starts wanting to write, or stops wanting to write.
383
/// They should return 0 on success, and _return_ an `errno` value on failure.
384
/// (Any `errno` value that they _set_ will be ignored.)
385
/// They will be passed `callback_data_ptr` as an argument.
386
///
387
/// If your program invokes `arti_rpc_*` from multiple threads,
388
/// these functions must be thread-safe.
389
///
390
/// There are additional requirements for these functions.
391
/// For full information, see the documentation for [`EventLoop`](crate::EventLoop`).
392
///
393
/// # Ownership
394
///
395
/// The caller is responsible for making sure that `*rpc_conn_out`,
396
/// `*rpc_poll_out`, and `*error_out`,
397
/// if set, are eventually freed.
398
///
399
/// The caller is responsible for making sure that `callback_data_ptr`,
400
/// and the callback functions,
401
/// live for at least as long as the returned `ArtiRpcPoll`.
402
#[allow(clippy::missing_safety_doc)]
403
#[unsafe(no_mangle)]
404
pub unsafe extern "C" fn arti_rpc_conn_builder_connect_polling(
405
    builder: *const ArtiRpcConnBuilder,
406
    start_writing_callback: Option<unsafe extern "C" fn(*mut c_void) -> c_int>,
407
    stop_writing_callback: Option<unsafe extern "C" fn(*mut c_void) -> c_int>,
408
    callback_data_ptr: *mut c_void,
409
    rpc_conn_out: *mut *mut ArtiRpcConn,
410
    rpc_poll_out: *mut *mut ArtiRpcPoll,
411
    error_out: *mut *mut ArtiRpcError,
412
) -> ArtiRpcStatus {
413
    ffi_body_with_err!(
414
        {
415
            let builder: Option<&ArtiRpcConnBuilder> [in_ptr_opt];
416
            let rpc_conn_out: Option<OutBoxedPtr<ArtiRpcConn>> [out_ptr_opt];
417
            let rpc_poll_out: Option<OutBoxedPtr<ArtiRpcPoll>> [out_ptr_opt];
418
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
419
        } in {
420
            let builder = builder.ok_or(InvalidInput::NullPointer)?;
421
            let start_writing_callback = start_writing_callback.ok_or(InvalidInput::NullPointer)?;
422
            let stop_writing_callback = stop_writing_callback.ok_or(InvalidInput::NullPointer)?;
423

            
424
            let event_loop = Box::new(UserEventLoop {
425
                start_writing_callback, stop_writing_callback,
426
                callback_data_ptr,
427
            });
428

            
429
            let b = builder.0.lock().expect("Poisoned lock");
430
            let (conn, poll) = b.connect_polling(event_loop)?;
431
            let poll = ArtiRpcPoll(Mutex::new(poll));
432
            rpc_conn_out.write_boxed_value_if_ptr_set(conn);
433
            rpc_poll_out.write_boxed_value_if_ptr_set(poll);
434
        }
435
    )
436
}
437

            
438
/// Given a pointer to an RPC connection, return the object ID for its negotiated session.
439
///
440
/// (The session was negotiated as part of establishing the connection.
441
/// Its object ID is necessary to invoke most other functionality on Arti.)
442
///
443
/// The caller should be prepared for a possible NULL return, in case somehow
444
/// no session was negotiated.
445
///
446
/// # Ownership
447
///
448
/// The resulting string is a reference to part of the `ArtiRpcConn`.
449
/// It lives for no longer than the underlying `ArtiRpcConn` object.
450
#[allow(clippy::missing_safety_doc)]
451
#[unsafe(no_mangle)]
452
pub unsafe extern "C" fn arti_rpc_conn_get_session_id(
453
    rpc_conn: *const ArtiRpcConn,
454
) -> *const c_char {
455
    ffi_body_raw! {
456
        {
457
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
458
        } in {
459
            rpc_conn.and_then(crate::RpcConn::session)
460
                .map(|s| s.as_ptr())
461
                .unwrap_or(std::ptr::null())
462
            // Safety: returned pointer is null, or semantically borrowed from `rpc_conn`.
463
            // It is only null if `rpc_conn` was null or its session was null.
464
            // The caller is not allowed to modify it.
465
        }
466
    }
467
}
468

            
469
/// Run an RPC request over `rpc_conn` and wait for a successful response.
470
///
471
/// The message `msg` should be a valid RPC request in JSON format.
472
/// If you omit its `id` field, one will be generated: this is typically the best way to use this function.
473
///
474
/// On success, return `ARTI_RPC_STATUS_SUCCESS` and set `*response_out` to a newly allocated string
475
/// containing the JSON response to your request (including `id` and `response` fields).
476
///
477
/// Otherwise return some other status code,  set `*response_out` to NULL,
478
/// and set `*error_out` (if provided) to a newly allocated error object.
479
///
480
/// (If response_out is set to NULL, then any successful response will be ignored.)
481
///
482
/// # Ownership
483
///
484
/// The caller is responsible for making sure that `*error_out`, if set, is eventually freed.
485
///
486
/// The caller is responsible for making sure that `*response_out`, if set, is eventually freed.
487
#[allow(clippy::missing_safety_doc)]
488
#[unsafe(no_mangle)]
489
pub unsafe extern "C" fn arti_rpc_conn_execute(
490
    rpc_conn: *const ArtiRpcConn,
491
    msg: *const c_char,
492
    response_out: *mut *mut ArtiRpcStr,
493
    error_out: *mut *mut ArtiRpcError,
494
) -> ArtiRpcStatus {
495
    ffi_body_with_err!(
496
        {
497
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
498
            let msg: Option<&str> [in_str_opt];
499
            let response_out: Option<OutBoxedPtr<ArtiRpcStr>> [out_ptr_opt];
500
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
501
        } in {
502
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
503
            let msg = msg.ok_or(InvalidInput::NullPointer)?;
504

            
505
            let success = rpc_conn.execute(msg)??;
506
            response_out.write_boxed_value_if_ptr_set(Utf8CString::from(success));
507
        }
508
    )
509
}
510

            
511
/// Send an RPC request over `rpc_conn`, and return a handle that can wait for a successful response.
512
///
513
/// The message `msg` should be a valid RPC request in JSON format.
514
/// If you omit its `id` field, one will be generated: this is typically the best way to use this function.
515
///
516
/// On success, return `ARTI_RPC_STATUS_SUCCESS` and set `*handle_out` to a newly allocated `ArtiRpcHandle`.
517
///
518
/// Otherwise return some other status code,  set `*handle_out` to NULL,
519
/// and set `*error_out` (if provided) to a newly allocated error object.
520
///
521
/// (If `handle_out` is set to NULL, the request will not be sent, and an error will be returned.)
522
///
523
/// # Ownership
524
///
525
/// The caller is responsible for making sure that `*error_out`, if set, is eventually freed.
526
///
527
/// The caller is responsible for making sure that `*handle_out`, if set, is eventually freed.
528
#[allow(clippy::missing_safety_doc)]
529
#[unsafe(no_mangle)]
530
pub unsafe extern "C" fn arti_rpc_conn_execute_with_handle(
531
    rpc_conn: *const ArtiRpcConn,
532
    msg: *const c_char,
533
    handle_out: *mut *mut ArtiRpcHandle,
534
    error_out: *mut *mut ArtiRpcError,
535
) -> ArtiRpcStatus {
536
    ffi_body_with_err!(
537
        {
538
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
539
            let msg: Option<&str> [in_str_opt];
540
            let handle_out: Option<OutBoxedPtr<ArtiRpcHandle>> [out_ptr_opt];
541
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
542
        } in {
543
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
544
            let msg = msg.ok_or(InvalidInput::NullPointer)?;
545
            let handle_out = handle_out.ok_or(InvalidInput::NullPointer)?;
546

            
547
            let handle = rpc_conn.execute_with_handle(msg)?;
548
            handle_out.write_value_boxed(handle);
549
        }
550
    )
551
}
552

            
553
/// Attempt to cancel the request on `rpc_conn` with the provided `handle`.
554
///
555
/// Note that cancellation _will_ fail if the handle has already been cancelled,
556
/// or has already succeeded or failed.
557
///
558
/// On success, return `ARTI_RPC_STATUS_SUCCESS`.
559
///
560
/// Otherwise return some other status code,
561
/// and set `*error_out` (if provided) to a newly allocated error object.
562
#[allow(clippy::missing_safety_doc)]
563
#[unsafe(no_mangle)]
564
pub unsafe extern "C" fn arti_rpc_conn_cancel_handle(
565
    rpc_conn: *const ArtiRpcConn,
566
    handle: *const ArtiRpcHandle,
567
    error_out: *mut *mut ArtiRpcError,
568
) -> ArtiRpcStatus {
569
    ffi_body_with_err!(
570
        {
571
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
572
            let handle: Option<&ArtiRpcHandle> [in_ptr_opt];
573
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
574
        } in {
575
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
576
            let handle = handle.ok_or(InvalidInput::NullPointer)?;
577
            let id = handle.id();
578
            rpc_conn.cancel(id)?;
579
        }
580
    )
581
}
582

            
583
/// A constant indicating that a message is a final result.
584
///
585
/// After a result has been received, a handle will not return any more responses,
586
/// and should be freed.
587
pub const ARTI_RPC_RESPONSE_TYPE_RESULT: ArtiRpcResponseType = 1;
588
/// A constant indicating that a message is a non-final update.
589
///
590
/// After an update has been received, the handle may return additional responses.
591
pub const ARTI_RPC_RESPONSE_TYPE_UPDATE: ArtiRpcResponseType = 2;
592
/// A constant indicating that a message is a final error.
593
///
594
/// After an error has been received, a handle will not return any more responses,
595
/// and should be freed.
596
pub const ARTI_RPC_RESPONSE_TYPE_ERROR: ArtiRpcResponseType = 3;
597

            
598
impl AnyResponse {
599
    /// Return an appropriate `ARTI_RPC_RESPONSE_TYPE_*` for this response.
600
    fn response_type(&self) -> ArtiRpcResponseType {
601
        match self {
602
            Self::Success(_) => ARTI_RPC_RESPONSE_TYPE_RESULT,
603
            Self::Update(_) => ARTI_RPC_RESPONSE_TYPE_UPDATE,
604
            Self::Error(_) => ARTI_RPC_RESPONSE_TYPE_ERROR,
605
        }
606
    }
607
}
608

            
609
/// Wait until some response arrives on an arti_rpc_handle, or until an error occurs.
610
///
611
/// On success, return `ARTI_RPC_STATUS_SUCCESS`; set `*response_out`, if present, to a
612
/// newly allocated string, and set `*response_type_out`, if present, to the type of the response.
613
/// (The type will be `ARTI_RPC_RESPONSE_TYPE_RESULT` if the response is a final result,
614
/// or `ARTI_RPC_RESPONSE_TYPE_ERROR` if the response is a final error,
615
/// or `ARTI_RPC_RESPONSE_TYPE_UPDATE` if the response is a non-final update.)
616
///
617
/// Otherwise return some other status code, set `*response_out` to NULL,
618
/// set `*response_type_out` to zero,
619
/// and set `*error_out` (if provided) to a newly allocated error object.
620
///
621
/// Note that receiving an error reply from Arti is _not_ treated as an error in this function.
622
/// That is to say, if Arti sends back an error, this function will return `ARTI_SUCCESS`,
623
/// and deliver the error from Arti in `*response_out`, setting `*response_type_out` to
624
/// `ARTI_RPC_RESPONSE_TYPE_ERROR`.
625
///
626
/// It is safe to call this function on the same handle from multiple threads at once.
627
/// If you do, each response will be sent to exactly one thread.
628
/// It is unspecified which thread will receive which response or which error.
629
///
630
/// # Ownership
631
///
632
/// The caller is responsible for making sure that `*error_out`, if set, is eventually freed.
633
///
634
/// The caller is responsible for making sure that `*response_out`, if set, is eventually freed.
635
#[allow(clippy::missing_safety_doc)]
636
#[unsafe(no_mangle)]
637
pub unsafe extern "C" fn arti_rpc_handle_wait(
638
    handle: *const ArtiRpcHandle,
639
    response_out: *mut *mut ArtiRpcStr,
640
    response_type_out: *mut ArtiRpcResponseType,
641
    error_out: *mut *mut ArtiRpcError,
642
) -> ArtiRpcStatus {
643
    ffi_body_with_err! {
644
        {
645
            let handle: Option<&ArtiRpcHandle> [in_ptr_opt];
646
            let response_out: Option<OutBoxedPtr<ArtiRpcStr>> [out_ptr_opt];
647
            let response_type_out: Option<OutVal<ArtiRpcResponseType>> [out_val_opt];
648
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
649
        } in {
650
            let handle = handle.ok_or(InvalidInput::NullPointer)?;
651

            
652
            let response = handle.wait_with_updates()?;
653

            
654
            let rtype = response.response_type();
655
            response_type_out.write_value_if_ptr_set(rtype);
656
            response_out.write_boxed_value_if_ptr_set(response.into_string());
657
        }
658
    }
659
}
660

            
661
/// Release storage held by an `ArtiRpcHandle`.
662
///
663
/// NOTE: This does not cancel the underlying request if it is still running.
664
/// To cancel a request, use `arti_rpc_conn_cancel_handle`.
665
#[allow(clippy::missing_safety_doc)]
666
#[unsafe(no_mangle)]
667
pub unsafe extern "C" fn arti_rpc_handle_free(handle: *mut ArtiRpcHandle) {
668
    ffi_body_raw!(
669
        {
670
            let handle: Option<Box<ArtiRpcHandle>> [in_ptr_consume_opt];
671
        } in {
672
            drop(handle);
673
            // Safety: Return value is (); trivially safe.
674
            ()
675
        }
676
    );
677
}
678

            
679
/// Submit an RPC request to `rpc_conn`, but do not wait for a response.
680
///
681
/// The message `msg` should be a valid RPC request in JSON format.
682
/// If you omit its `id` field, one will be generated:
683
/// this is typically the best way to use this function.
684
///
685
/// The `tag` value should be a value to identify this request;
686
/// it will be returned later along with any responses to this request.
687
///
688
/// On success, return `ARTI_RPC_STATUS_SUCCESS`.
689
///
690
/// Otherwise return some other status code,  set `*response_out` to NULL,
691
/// and set `*error_out` (if provided) to a newly allocated error object.
692
///
693
/// After calling this function, the caller must later make sure
694
/// that [`arti_rpc_conn_wait()`] is called on the connection to wait for responses
695
/// to _any_ submitted request.
696
/// (If the  connection was crated with [`arti_rpc_conn_builder_connect_polling`],
697
/// the user must call [`arti_rpc_poll_poll()`] instead.)
698
///
699
/// (If nobody is running [`arti_rpc_conn_wait()`] or [`arti_rpc_poll_poll()`],
700
/// then responses will never be handled,
701
/// and can potentially fill up memory.)
702
///
703
/// # Ownership
704
///
705
/// The caller is responsible for making sure that `*error_out`, if set, is eventually freed.
706
///
707
/// # Thread safety
708
///
709
/// It is safe to call this function from multiple threads at once;
710
/// it is not specified which thread will receive notifications for which request.
711
#[allow(clippy::missing_safety_doc)]
712
#[unsafe(no_mangle)]
713
pub unsafe extern "C" fn arti_rpc_conn_submit(
714
    rpc_conn: *const ArtiRpcConn,
715
    msg: *const c_char,
716
    tag: *const ArtiRpcUserTag,
717
    error_out: *mut *mut ArtiRpcError,
718
) -> ArtiRpcStatus {
719
    ffi_body_with_err!(
720
        {
721
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
722
            let msg: Option<&str> [in_str_opt];
723
            let tag: Option<&ArtiRpcUserTag> [in_ptr_opt];
724
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
725
        } in {
726
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
727
            let msg = msg.ok_or(InvalidInput::NullPointer)?;
728
            let tag = tag.ok_or(InvalidInput::NullPointer)?;
729

            
730
            let () = rpc_conn.submit((*tag).into(), msg)?;
731
        }
732
    )
733
}
734

            
735
/// Wait for responses to arrive for requests sent via [`arti_rpc_conn_submit`].
736
///
737
/// On success, return `ARTI_RPC_STATUS_SUCCESS`;
738
/// set `*tag_out`, if present,  to the `ArtiRpcUserTag` originally provided with the request;
739
/// set `*response_out`, if present, to a newly allocated string;
740
/// and set `*response_type_out`,  if present, to the type of the response.
741
/// (The type will be `ARTI_RPC_RESPONSE_TYPE_RESULT` if the response is a final result,
742
/// or `ARTI_RPC_RESPONSE_TYPE_ERROR` if the response is a final error,
743
/// or `ARTI_RPC_RESPONSE_TYPE_UPDATE` if the response is a non-final update.)
744
///
745
/// Otherwise return some other status code, set `*response_out` to NULL,
746
/// set `*response_type_out` and `*tag_out` to zero,
747
/// and set `*error_out` (if provided) to a newly allocated error object.
748
///
749
/// Note that receiving an error reply from Arti is _not_ treated as an error in this function.
750
/// That is to say, if Arti sends back an error, this function will return `ARTI_SUCCESS`,
751
/// and deliver the error from Arti in `*response_out`, setting `*response_type_out` to
752
/// `ARTI_RPC_RESPONSE_TYPE_ERROR`.
753
///
754
/// # Thread safety
755
///
756
/// It is safe to call this function from multiple threads at once;
757
/// it is not specified which thread will receive notifications for which request.
758
///
759
/// # Ownership
760
///
761
/// The caller is responsible for making sure that `*error_out`, if set, is eventually freed.
762
///
763
/// The caller is responsible for making sure that `*response_out`, if set,
764
/// is eventually freed.
765
#[allow(clippy::missing_safety_doc)]
766
#[unsafe(no_mangle)]
767
pub unsafe extern "C" fn arti_rpc_conn_wait(
768
    rpc_conn: *const ArtiRpcConn,
769
    tag_out: *mut ArtiRpcUserTag,
770
    response_out: *mut *mut ArtiRpcStr,
771
    response_type_out: *mut ArtiRpcResponseType,
772
    error_out: *mut *mut ArtiRpcError,
773
) -> ArtiRpcStatus {
774
    ffi_body_with_err!(
775
        {
776
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
777
            let tag_out: Option<OutVal<ArtiRpcUserTag>> [out_val_opt];
778
            let response_out: Option<OutBoxedPtr<ArtiRpcStr>> [out_ptr_opt];
779
            let response_type_out: Option<OutVal<ArtiRpcResponseType>> [out_val_opt];
780
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
781
        } in {
782
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
783

            
784
            let (tag, response) = rpc_conn.wait()?;
785
            tag_out.write_value_if_ptr_set(tag.into());
786

            
787
            let rtype = response.response_type();
788
            response_type_out.write_value_if_ptr_set(rtype);
789
            response_out.write_boxed_value_if_ptr_set(response.into_string());
790
        }
791
    )
792
}
793

            
794
/// Handle IO events for the associated RPC connection, without blocking.
795
///
796
/// This method requires that the connection was created with
797
/// [`arti_rpc_conn_builder_connect_polling()`].
798
/// It reads and writes data from the RPC server, until either:
799
///
800
/// * A response is available to a request created with [`arti_rpc_conn_submit`].
801
///   In this case,
802
///   `*tag_out`, if present, is set to the `ArtiRpcUserTag` originally provided with the request;
803
///   `*response_out`, if present, is set to a newly allocated string;
804
///   `*response_type_out`, if present, is set to the type of the response.
805
///   The `*would_block_out` flag, if present, is set to 0.
806
///   Other output pointers, if present, are set to NULL or 0.
807
///
808
/// * No further progress can be made without blocking.
809
///   In this case,
810
///   `*would_block_out` is set to 1.
811
///   Other output pointers, if present, are set to NULL or 0.
812
///
813
/// * An error occurs.
814
///   (This does not include receiving an error response from the RPC server.)
815
///   In this case, `*error_out`, if present, is set to that error.
816
///   Other output pointers, if present, are set to NULL or 0.
817
///
818
/// Returns `ARTI_RPC_SUCCESS` in the first two cases,
819
/// and an error code on failure.
820
///
821
/// # Thread safety
822
///
823
/// While it is not unsafe to call this function at once,
824
/// it is generally pointless:
825
/// only one thread can make progress at a time.
826
///
827
/// # Ownership
828
///
829
/// The caller is responsible for making sure
830
/// that `*tag_out`, *response_out`, and `*error_out`,
831
/// if set, are eventually freed.
832
#[allow(clippy::missing_safety_doc)]
833
#[unsafe(no_mangle)]
834
pub unsafe extern "C" fn arti_rpc_poll_poll(
835
    rpc_poll: *const ArtiRpcPoll,
836
    tag_out: *mut ArtiRpcUserTag,
837
    response_out: *mut *mut ArtiRpcStr,
838
    response_type_out: *mut ArtiRpcResponseType,
839
    would_block_out: *mut c_int,
840
    error_out: *mut *mut ArtiRpcError,
841
) -> ArtiRpcStatus {
842
    ffi_body_with_err!({
843
        let rpc_poll: Option<&ArtiRpcPoll> [in_ptr_opt];
844
        let tag_out: Option<OutVal<ArtiRpcUserTag>> [out_val_opt];
845
        let response_out: Option<OutBoxedPtr<ArtiRpcStr>> [out_ptr_opt];
846
        let response_type_out: Option<OutVal<ArtiRpcResponseType>> [out_val_opt];
847
        let would_block_out: Option<OutVal<c_int>> [out_val_opt];
848
        err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
849
    } in {
850
        let rpc_poll = rpc_poll.ok_or(InvalidInput::NullPointer)?;
851

            
852
        let (tag, response)  = match rpc_poll.0.lock().expect("Lock poisoned").poll()? {
853
            Ok(v) => v,
854
            Err(crate::WouldBlock) => {
855
                would_block_out.write_value_if_ptr_set(1);
856
                return Ok(());
857
            }
858
        };
859

            
860
        tag_out.write_value_if_ptr_set(tag.into());
861
        let rtype = response.response_type();
862
        response_type_out.write_value_if_ptr_set(rtype);
863
        response_out.write_boxed_value_if_ptr_set(response.into_string());
864
    })
865
}
866

            
867
/// Return true if `rpc_poll` wants to write,
868
/// and false otherwise.
869
///
870
/// See [`arti_rpc_conn_builder_connect_polling`] for more information.
871
#[allow(clippy::missing_safety_doc)]
872
#[unsafe(no_mangle)]
873
pub unsafe extern "C" fn arti_rpc_poll_wants_to_write(rpc_poll: *const ArtiRpcPoll) -> c_int {
874
    ffi_body_raw!({
875
        let rpc_poll: Option<&ArtiRpcPoll> [in_ptr_opt];
876
    } in {
877
        let Some(rpc_poll) = rpc_poll else {
878
            return 0;
879
        };
880
        let wants_to_write: bool = rpc_poll.0.lock()
881
            .expect("Lock poisoned")
882
            .wants_to_write();
883
        // Safety: return type is c_int; trivially safe.
884
        c_int::from(wants_to_write)
885
    })
886
}
887

            
888
/// Return the raw OS socket associated with the provided `ArtiRpcPoll`.
889
///
890
/// This function returns a SOCKET on windows, and a file descriptor elsewhere.
891
///
892
/// On failure, returns INVALID_SOCKET on windows, and -1 elsewhere.
893
///
894
/// # Ownership
895
///
896
/// The returned socket is owned by the `ArtiRpcPoll`.
897
/// The caller must not close it, read from it, or write to it.
898
/// It should _only_ be polled for readiness events.
899
#[allow(clippy::missing_safety_doc)]
900
#[unsafe(no_mangle)]
901
pub unsafe extern "C" fn arti_rpc_poll_get_socket(
902
    rpc_poll: *const ArtiRpcPoll,
903
) -> ArtiRpcRawSocket {
904
    ffi_body_raw!({
905
        let rpc_poll: Option<&ArtiRpcPoll> [in_ptr_opt];
906
    } in {
907
        let Some(rpc_poll) = rpc_poll else {
908
            return ArtiRpcRawSocket::default();
909
        };
910
        let raw_socket: Result<ArtiRpcRawSocket, _> = {
911
            let rpc_poll = rpc_poll.0.lock().expect("Lock poisoned");
912
            #[cfg(windows)]
913
            {
914
                rpc_poll.try_as_socket().map(ArtiRpcRawSocket::from)
915
            }
916
            #[cfg(not(windows))]
917
            {
918
                rpc_poll.try_as_fd().map(ArtiRpcRawSocket::from)
919
            }
920
        };
921

            
922
        // Safety: return type is trivially safe.
923
        raw_socket.unwrap_or_default()
924
    })
925
}
926

            
927
/// Free the provided `ArtiRpcPoll`.
928
#[allow(clippy::missing_safety_doc)]
929
#[unsafe(no_mangle)]
930
pub unsafe extern "C" fn arti_rpc_poll_free(rpc_poll: *mut ArtiRpcPoll) {
931
    ffi_body_raw!(
932
        {
933
            let rpc_poll: Option<Box<ArtiRpcPoll>> [in_ptr_consume_opt];
934
        } in {
935
            drop(rpc_poll);
936
            // Safety: Return value is (); trivially safe.
937
            ()
938
        }
939
    );
940
}
941

            
942
/// Free a string returned by the Arti RPC API.
943
#[allow(clippy::missing_safety_doc)]
944
#[unsafe(no_mangle)]
945
pub unsafe extern "C" fn arti_rpc_str_free(string: *mut ArtiRpcStr) {
946
    ffi_body_raw!(
947
        {
948
            let string: Option<Box<ArtiRpcStr>> [in_ptr_consume_opt];
949
        } in {
950
            drop(string);
951
            // Safety: Return value is (); trivially safe.
952
            ()
953
        }
954
    );
955
}
956

            
957
/// Return a const pointer to the underlying nul-terminated string from an `ArtiRpcStr`.
958
///
959
/// The resulting string is guaranteed to be valid UTF-8.
960
///
961
/// (Returns NULL if the input is NULL.)
962
///
963
/// # Correctness requirements
964
///
965
/// The resulting string pointer is valid only for as long as the input `string` is not freed.
966
#[allow(clippy::missing_safety_doc)]
967
#[unsafe(no_mangle)]
968
pub unsafe extern "C" fn arti_rpc_str_get(string: *const ArtiRpcStr) -> *const c_char {
969
    ffi_body_raw!(
970
        {
971
            let string: Option<&ArtiRpcStr> [in_ptr_opt];
972
        } in {
973
            // Safety: returned pointer is null, or semantically borrowed from `string`.
974
            // It is only null if `string` was null.
975
            // The caller is not allowed to modify it.
976
            match string {
977
                Some(s) => s.as_ptr(),
978
                None => std::ptr::null(),
979
            }
980

            
981
        }
982
    )
983
}
984

            
985
/// Close and free an open Arti RPC connection.
986
#[allow(clippy::missing_safety_doc)]
987
#[unsafe(no_mangle)]
988
pub unsafe extern "C" fn arti_rpc_conn_free(rpc_conn: *mut ArtiRpcConn) {
989
    ffi_body_raw!(
990
        {
991
            let rpc_conn: Option<Box<ArtiRpcConn>> [in_ptr_consume_opt];
992
        } in {
993
            drop(rpc_conn);
994
            // Safety: Return value is (); trivially safe.
995
            ()
996

            
997
        }
998
    );
999
}
/// Try to open an anonymized data stream over Arti.
///
/// Use the proxy information associated with `rpc_conn` to make the stream,
/// and store the resulting fd (or `SOCKET` on Windows) into `*socket_out`.
///
/// The stream will target the address `hostname`:`port`.
///
/// If `on_object` is provided, it is an `ObjectId` for client-like object
/// (such as a Session or a Client)
/// that should be used to make the stream.
///
/// The resulting stream will be configured
/// not to share a circuit with any other stream
/// having a different `isolation`.
/// (If your application doesn't care about isolating its streams from one another,
/// it is acceptable to leave `isolation` as an empty string.)
///
/// If `stream_id_out` is provided,
/// the resulting stream will have an identifier within the RPC system,
/// so that you can run other RPC commands on it.
///
/// On success, return `ARTI_RPC_STATUS_SUCCESS`.
/// Otherwise return some other status code, set `*socket_out` to -1
/// (or `INVALID_SOCKET` on Windows),
/// and set `*error_out` (if provided) to a newly allocated error object.
///
/// # Caveats
///
/// When possible, use a hostname rather than an IP address.
/// If you *must* use an IP address, make sure that you have not gotten it
/// by a non-anonymous DNS lookup.
/// (Calling `gethostname()` or `getaddrinfo()` directly
/// would lose anonymity: they inform the user's DNS server,
/// and possibly many other parties, about the target address
/// you are trying to visit.)
///
/// The resulting socket will actually be a TCP connection to Arti,
/// not directly to your destination.
/// Therefore, passing it to functions like `getpeername()`
/// may give unexpected results.
///
/// If `stream_id_out` is provided,
/// the caller is responsible for releasing the ObjectId;
/// Arti will not deallocate it even when the stream is closed.
///
/// # Ownership
///
/// The caller is responsible for making sure that
/// `*stream_id_out` and `*error_out`, if set,
/// are eventually freed.
///
/// The caller is responsible for making sure that `*socket_out`, if set,
/// is eventually closed.
#[allow(clippy::missing_safety_doc)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn arti_rpc_conn_open_stream(
    rpc_conn: *const ArtiRpcConn,
    hostname: *const c_char,
    port: c_int,
    on_object: *const c_char,
    isolation: *const c_char,
    socket_out: *mut ArtiRpcRawSocket,
    stream_id_out: *mut *mut ArtiRpcStr,
    error_out: *mut *mut ArtiRpcError,
) -> ArtiRpcStatus {
    ffi_body_with_err! {
        {
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
            let on_object: Option<&str> [in_str_opt];
            let hostname: Option<&str> [in_str_opt];
            let isolation: Option<&str> [in_str_opt];
            let socket_out: Option<OutSocketOwned<'_>> [out_socket_owned_opt];
            let stream_id_out: Option<OutBoxedPtr<ArtiRpcStr>> [out_ptr_opt];
            err error_out: Option<OutBoxedPtr<ArtiRpcError>>;
        } in {
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
            let hostname = hostname.ok_or(InvalidInput::NullPointer)?;
            let socket_out = socket_out.ok_or(InvalidInput::NullPointer)?;
            let isolation = isolation.ok_or(InvalidInput::NullPointer)?;
            let port: u16 = port.try_into().map_err(|_| InvalidInput::BadPort)?;
            if port == 0 {
                return Err(InvalidInput::BadPort.into());
            }
            let on_object = on_object.map(|o| ObjectId::try_from(o.to_owned()))
                .transpose()
                .expect("C string somehow contained NUL.");
            let stream = match stream_id_out {
                Some(stream_id_out) => {
                    let (stream_id, stream) = rpc_conn.open_stream_as_object(
                        on_object.as_ref(),
                        (hostname, port),
                        isolation)?;
                    stream_id_out.write_value_boxed(stream_id.into());
                    stream
                }
                None => {
                    rpc_conn.open_stream(on_object.as_ref(), (hostname, port), isolation)?
                }
            };
            // We call this last so that the stream will definitely be converted to an fd, or
            // dropped.
            socket_out.write_socket(stream);
        }
    }
}