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};
11
use std::sync::Mutex;
12
use util::{
13
    ffi_body_raw, ffi_body_with_err, OptOutPtrExt as _, OptOutValExt, OutPtr, OutSocketOwned,
14
    OutVal,
15
};
16

            
17
use crate::{
18
    conn::{AnyResponse, RequestHandle},
19
    util::Utf8CString,
20
    ObjectId, RpcConnBuilder,
21
};
22

            
23
/// A status code returned by an Arti RPC function.
24
///
25
/// On success, a function will return `ARTI_SUCCESS (0)`.
26
/// On failure, a function will return some other status code.
27
pub type ArtiRpcStatus = u32;
28

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

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

            
45
/// An owned string, returned by this library.
46
///
47
/// This string must be released with `arti_rpc_str_free`.
48
/// You can inspect it with `arti_rpc_str_get`, but you may not modify it.
49
/// The string is guaranteed to be UTF-8 and NUL-terminated.
50
pub type ArtiRpcStr = Utf8CString;
51

            
52
/// A handle to an in-progress RPC request.
53
///
54
/// This handle must eventually be freed with `arti_rpc_handle_free`.
55
///
56
/// You can wait for the next message with `arti_rpc_handle_wait`.
57
pub type ArtiRpcHandle = RequestHandle;
58

            
59
/// The type of a message returned by an RPC request.
60
pub type ArtiRpcResponseType = c_int;
61

            
62
/// The type of an entry prepended to a connect point search path.
63
pub type ArtiRpcBuilderEntryType = c_int;
64

            
65
/// The type of a data stream socket.
66
/// (This is always `int` on Unix-like platforms,
67
/// and SOCKET on Windows.)
68
//
69
// NOTE: We declare this as a separate type so that we can give it a default.
70
#[repr(transparent)]
71
pub struct ArtiRpcRawSocket(
72
    #[cfg(windows)] std::os::windows::raw::SOCKET,
73
    #[cfg(not(windows))] c_int,
74
);
75

            
76
impl Default for ArtiRpcRawSocket {
77
    fn default() -> Self {
78
        #[cfg(windows)]
79
        {
80
            Self(!0)
81
        }
82
        #[cfg(not(windows))]
83
        {
84
            Self(-1)
85
        }
86
    }
87
}
88

            
89
/// Try to create a new `ArtiRpcConnBuilder`, with default settings.
90
///
91
/// On success, return `ARTI_RPC_STATUS_SUCCESS` and set `*builder_out`
92
/// to a new `ArtiRpcConnBuilder`.
93
/// Otherwise return some other status code, set `*builder_out` to NULL, and set
94
/// `*error_out` (if provided) to a newly allocated error object.
95
///
96
/// # Ownership
97
///
98
/// The caller is responsible for making sure that `*builder_out` and `*error_out`,
99
/// if set, are eventually freed.
100
#[allow(clippy::missing_safety_doc)]
101
#[no_mangle]
102
pub unsafe extern "C" fn arti_rpc_conn_builder_new(
103
    builder_out: *mut *mut ArtiRpcConnBuilder,
104
    error_out: *mut *mut ArtiRpcError,
105
) -> ArtiRpcStatus {
106
    ffi_body_with_err!(
107
        {
108
            let builder_out: Option<OutPtr<ArtiRpcConnBuilder>> [out_ptr_opt];
109
            err error_out: Option<OutPtr<ArtiRpcError>>;
110
        } in {
111
            let builder = ArtiRpcConnBuilder(Mutex::new(RpcConnBuilder::new()));
112
            builder_out.write_boxed_value_if_ptr_set(builder);
113
        }
114
    )
115
}
116

            
117
/// Release storage held by an `ArtiRpcConnBuilder`.
118
#[allow(clippy::missing_safety_doc)]
119
#[no_mangle]
120
pub unsafe extern "C" fn arti_rpc_conn_builder_free(builder: *mut ArtiRpcConnBuilder) {
121
    ffi_body_raw!(
122
        {
123
            let builder: Option<Box<ArtiRpcConnBuilder>> [in_ptr_consume_opt];
124
        } in {
125
            drop(builder);
126
            // Safety: return value is (); trivially safe.
127
            ()
128
        }
129
    );
130
}
131

            
132
/// Constant to denote a literal connect point.
133
///
134
/// This constant is passed to [`arti_rpc_conn_builder_prepend_entry`].
135
pub const ARTI_RPC_BUILDER_ENTRY_LITERAL_CONNECT_POINT: ArtiRpcBuilderEntryType = 1;
136

            
137
/// Constant to denote a path in which Arti configuration variables are expanded.
138
///
139
/// This constant is passed to [`arti_rpc_conn_builder_prepend_entry`].
140
pub const ARTI_RPC_BUILDER_ENTRY_EXPANDABLE_PATH: ArtiRpcBuilderEntryType = 2;
141

            
142
/// Constant to denote a literal path that is not expanded.
143
///
144
/// This constant is passed to [`arti_rpc_conn_builder_prepend_entry`].
145
pub const ARTI_RPC_BUILDER_ENTRY_LITERAL_PATH: ArtiRpcBuilderEntryType = 3;
146

            
147
/// Prepend a single entry to the connection point path in `builder`.
148
///
149
/// This entry will be considered before any entries in `${ARTI_RPC_CONNECT_PATH}`,
150
/// but after any entry in `${ARTI_RPC_CONNECT_PATH_OVERRIDE}`.
151
///
152
/// The interpretation will depend on the value of `entry_type`.
153
///
154
/// On success, return `ARTI_RPC_STATUS_SUCCESS`.
155
/// Otherwise return some other status code, and set
156
/// `*error_out` (if provided) to a newly allocated error object.
157
///
158
/// # Ownership
159
///
160
/// The caller is responsible for making sure that `*error_out`,
161
/// if set, is eventually freed.
162
#[allow(clippy::missing_safety_doc)]
163
#[no_mangle]
164
pub unsafe extern "C" fn arti_rpc_conn_builder_prepend_entry(
165
    builder: *const ArtiRpcConnBuilder,
166
    entry_type: ArtiRpcBuilderEntryType,
167
    entry: *const c_char,
168
    error_out: *mut *mut ArtiRpcError,
169
) -> ArtiRpcStatus {
170
    ffi_body_with_err!(
171
        {
172
            let builder: Option<&ArtiRpcConnBuilder> [in_ptr_opt];
173
            let entry: Option<&str> [in_str_opt];
174
            err error_out: Option<OutPtr<ArtiRpcError>>;
175
        } in {
176
            let builder = builder.ok_or(InvalidInput::NullPointer)?;
177
            let entry = entry.ok_or(InvalidInput::NullPointer)?;
178
            let mut b = builder.0.lock().expect("Poisoned lock");
179
            match entry_type {
180
                ARTI_RPC_BUILDER_ENTRY_LITERAL_CONNECT_POINT =>
181
                    b.prepend_literal_entry(entry.to_string()),
182
                ARTI_RPC_BUILDER_ENTRY_EXPANDABLE_PATH =>
183
                    b.prepend_path(entry.into()),
184
                ARTI_RPC_BUILDER_ENTRY_LITERAL_PATH =>
185
                    b.prepend_literal_path(entry.into()),
186
                _ => return Err(InvalidInput::InvalidConstValue.into()),
187
            }
188
        }
189
    )
190
}
191

            
192
/// Use `builder` to open a new RPC connection to Arti.
193
///
194
/// On success, return `ARTI_RPC_STATUS_SUCCESS`,
195
/// and set `conn_out` to a new ArtiRpcConn.
196
/// Otherwise return some other status code, set *conn_out to NULL, and set
197
/// `*error_out` (if provided) to a newly allocated error object.
198
///
199
/// # Ownership
200
///
201
/// The caller is responsible for making sure that `*rpc_conn_out` and `*error_out`,
202
/// if set, are eventually freed.
203
#[allow(clippy::missing_safety_doc)]
204
#[no_mangle]
205
pub unsafe extern "C" fn arti_rpc_conn_builder_connect(
206
    builder: *const ArtiRpcConnBuilder,
207
    rpc_conn_out: *mut *mut ArtiRpcConn,
208
    error_out: *mut *mut ArtiRpcError,
209
) -> ArtiRpcStatus {
210
    ffi_body_with_err!(
211
        {
212
            let builder: Option<&ArtiRpcConnBuilder> [in_ptr_opt];
213
            let rpc_conn_out: Option<OutPtr<ArtiRpcConn>> [out_ptr_opt];
214
            err error_out: Option<OutPtr<ArtiRpcError>>;
215
        } in {
216
            let builder = builder.ok_or(InvalidInput::NullPointer)?;
217
            let b = builder.0.lock().expect("Poisoned lock");
218
            let conn = b.connect()?;
219
            rpc_conn_out.write_boxed_value_if_ptr_set(conn);
220
        }
221
    )
222
}
223

            
224
/// Given a pointer to an RPC connection, return the object ID for its negotiated session.
225
///
226
/// (The session was negotiated as part of establishing the connection.
227
/// Its object ID is necessary to invoke most other functionality on Arti.)
228
///
229
/// The caller should be prepared for a possible NULL return, in case somehow
230
/// no session was negotiated.
231
///
232
/// # Ownership
233
///
234
/// The resulting string is a reference to part of the `ArtiRpcConn`.
235
/// It lives for no longer than the underlying `ArtiRpcConn` object.
236
#[allow(clippy::missing_safety_doc)]
237
#[no_mangle]
238
pub unsafe extern "C" fn arti_rpc_conn_get_session_id(
239
    rpc_conn: *const ArtiRpcConn,
240
) -> *const c_char {
241
    ffi_body_raw! {
242
        {
243
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
244
        } in {
245
            rpc_conn.and_then(crate::RpcConn::session)
246
                .map(|s| s.as_ptr())
247
                .unwrap_or(std::ptr::null())
248
            // Safety: returned pointer is null, or semantically borrowed from `rpc_conn`.
249
            // It is only null if `rpc_conn` was null or its session was null.
250
            // The caller is not allowed to modify it.
251
        }
252
    }
253
}
254

            
255
/// Run an RPC request over `rpc_conn` and wait for a successful response.
256
///
257
/// The message `msg` should be a valid RPC request in JSON format.
258
/// If you omit its `id` field, one will be generated: this is typically the best way to use this function.
259
///
260
/// On success, return `ARTI_RPC_STATUS_SUCCESS` and set `*response_out` to a newly allocated string
261
/// containing the JSON response to your request (including `id` and `response` fields).
262
///
263
/// Otherwise return some other status code,  set `*response_out` to NULL,
264
/// and set `*error_out` (if provided) to a newly allocated error object.
265
///
266
/// (If response_out is set to NULL, then any successful response will be ignored.)
267
///
268
/// # Ownership
269
///
270
/// The caller is responsible for making sure that `*error_out`, if set, is eventually freed.
271
///
272
/// The caller is responsible for making sure that `*response_out`, if set, is eventually freed.
273
#[allow(clippy::missing_safety_doc)]
274
#[no_mangle]
275
pub unsafe extern "C" fn arti_rpc_conn_execute(
276
    rpc_conn: *const ArtiRpcConn,
277
    msg: *const c_char,
278
    response_out: *mut *mut ArtiRpcStr,
279
    error_out: *mut *mut ArtiRpcError,
280
) -> ArtiRpcStatus {
281
    ffi_body_with_err!(
282
        {
283
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
284
            let msg: Option<&str> [in_str_opt];
285
            let response_out: Option<OutPtr<ArtiRpcStr>> [out_ptr_opt];
286
            err error_out: Option<OutPtr<ArtiRpcError>>;
287
        } in {
288
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
289
            let msg = msg.ok_or(InvalidInput::NullPointer)?;
290

            
291
            let success = rpc_conn.execute(msg)??;
292
            response_out.write_boxed_value_if_ptr_set(Utf8CString::from(success));
293
        }
294
    )
295
}
296

            
297
/// Send an RPC request over `rpc_conn`, and return a handle that can wait for a successful response.
298
///
299
/// The message `msg` should be a valid RPC request in JSON format.
300
/// If you omit its `id` field, one will be generated: this is typically the best way to use this function.
301
///
302
/// On success, return `ARTI_RPC_STATUS_SUCCESS` and set `*handle_out` to a newly allocated `ArtiRpcHandle`.
303
///
304
/// Otherwise return some other status code,  set `*handle_out` to NULL,
305
/// and set `*error_out` (if provided) to a newly allocated error object.
306
///
307
/// (If `handle_out` is set to NULL, the request will not be sent, and an error will be returned.)
308
///
309
/// # Ownership
310
///
311
/// The caller is responsible for making sure that `*error_out`, if set, is eventually freed.
312
///
313
/// The caller is responsible for making sure that `*handle_out`, if set, is eventually freed.
314
#[allow(clippy::missing_safety_doc)]
315
#[no_mangle]
316
pub unsafe extern "C" fn arti_rpc_conn_execute_with_handle(
317
    rpc_conn: *const ArtiRpcConn,
318
    msg: *const c_char,
319
    handle_out: *mut *mut ArtiRpcHandle,
320
    error_out: *mut *mut ArtiRpcError,
321
) -> ArtiRpcStatus {
322
    ffi_body_with_err!(
323
        {
324
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
325
            let msg: Option<&str> [in_str_opt];
326
            let handle_out: Option<OutPtr<ArtiRpcHandle>> [out_ptr_opt];
327
            err error_out: Option<OutPtr<ArtiRpcError>>;
328
        } in {
329
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
330
            let msg = msg.ok_or(InvalidInput::NullPointer)?;
331
            let handle_out = handle_out.ok_or(InvalidInput::NullPointer)?;
332

            
333
            let handle = rpc_conn.execute_with_handle(msg)?;
334
            handle_out.write_value_boxed(handle);
335
        }
336
    )
337
}
338

            
339
/// Attempt to cancel the request on `rpc_conn` with the provided `handle`.
340
///
341
/// Note that cancellation _will_ fail if the handle has already been cancelled,
342
/// or has already succeeded or failed.
343
///
344
/// On success, return `ARTI_RPC_STATUS_SUCCESS`.
345
///
346
/// Otherwise return some other status code,
347
/// and set `*error_out` (if provided) to a newly allocated error object.
348
#[allow(clippy::missing_safety_doc)]
349
#[no_mangle]
350
pub unsafe extern "C" fn arti_rpc_conn_cancel_handle(
351
    rpc_conn: *const ArtiRpcConn,
352
    handle: *const ArtiRpcHandle,
353
    error_out: *mut *mut ArtiRpcError,
354
) -> ArtiRpcStatus {
355
    ffi_body_with_err!(
356
        {
357
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
358
            let handle: Option<&ArtiRpcHandle> [in_ptr_opt];
359
            err error_out: Option<OutPtr<ArtiRpcError>>;
360
        } in {
361
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
362
            let handle = handle.ok_or(InvalidInput::NullPointer)?;
363
            let id = handle.id();
364
            rpc_conn.cancel(id)?;
365
        }
366
    )
367
}
368

            
369
/// A constant indicating that a message is a final result.
370
///
371
/// After a result has been received, a handle will not return any more responses,
372
/// and should be freed.
373
pub const ARTI_RPC_RESPONSE_TYPE_RESULT: ArtiRpcResponseType = 1;
374
/// A constant indicating that a message is a non-final update.
375
///
376
/// After an update has been received, the handle may return additional responses.
377
pub const ARTI_RPC_RESPONSE_TYPE_UPDATE: ArtiRpcResponseType = 2;
378
/// A constant indicating that a message is a final error.
379
///
380
/// After an error has been received, a handle will not return any more responses,
381
/// and should be freed.
382
pub const ARTI_RPC_RESPONSE_TYPE_ERROR: ArtiRpcResponseType = 3;
383

            
384
impl AnyResponse {
385
    /// Return an appropriate `ARTI_RPC_RESPONSE_TYPE_*` for this response.
386
    fn response_type(&self) -> ArtiRpcResponseType {
387
        match self {
388
            Self::Success(_) => ARTI_RPC_RESPONSE_TYPE_RESULT,
389
            Self::Update(_) => ARTI_RPC_RESPONSE_TYPE_UPDATE,
390
            Self::Error(_) => ARTI_RPC_RESPONSE_TYPE_ERROR,
391
        }
392
    }
393
}
394

            
395
/// Wait until some response arrives on an arti_rpc_handle, or until an error occurs.
396
///
397
/// On success, return `ARTI_RPC_STATUS_SUCCESS`; set `*response_out`, if present, to a
398
/// newly allocated string, and set `*response_type_out`, to the type of the response.
399
/// (The type will be `ARTI_RPC_RESPONSE_TYPE_RESULT` if the response is a final result,
400
/// or `ARTI_RPC_RESPONSE_TYPE_ERROR` if the response is a final error,
401
/// or `ARTI_RPC_RESPONSE_TYPE_UPDATE` if the response is a non-final update.)
402
///
403
/// Otherwise return some other status code, set `*response_out` to NULL,
404
/// set `*response_type_out` to zero,
405
/// and set `*error_out` (if provided) to a newly allocated error object.
406
///
407
/// Note that receiving an error reply from Arti is _not_ treated as an error in this function.
408
/// That is to say, if Arti sends back an error, this function will return `ARTI_SUCCESS`,
409
/// and deliver the error from Arti in `*response_out`, setting `*response_type_out` to
410
/// `ARTI_RPC_RESPONSE_TYPE_ERROR`.
411
///
412
/// It is safe to call this function on the same handle from multiple threads at once.
413
/// If you do, each response will be sent to exactly one thread.
414
/// It is unspecified which thread will receive which response or which error.
415
///
416
/// # Ownership
417
///
418
/// The caller is responsible for making sure that `*error_out`, if set, is eventually freed.
419
///
420
/// The caller is responsible for making sure that `*response_out`, if set, is eventually freed.
421
#[allow(clippy::missing_safety_doc)]
422
#[no_mangle]
423
pub unsafe extern "C" fn arti_rpc_handle_wait(
424
    handle: *const ArtiRpcHandle,
425
    response_out: *mut *mut ArtiRpcStr,
426
    response_type_out: *mut ArtiRpcResponseType,
427
    error_out: *mut *mut ArtiRpcError,
428
) -> ArtiRpcStatus {
429
    ffi_body_with_err! {
430
        {
431
            let handle: Option<&ArtiRpcHandle> [in_ptr_opt];
432
            let response_out: Option<OutPtr<ArtiRpcStr>> [out_ptr_opt];
433
            let response_type_out: Option<OutVal<ArtiRpcResponseType>> [out_val_opt];
434
            err error_out: Option<OutPtr<ArtiRpcError>>;
435
        } in {
436
            let handle = handle.ok_or(InvalidInput::NullPointer)?;
437

            
438
            let response = handle.wait_with_updates()?;
439

            
440
            let rtype = response.response_type();
441
            response_type_out.write_value_if_ptr_set(rtype);
442
            response_out.write_boxed_value_if_ptr_set(response.into_string());
443
        }
444
    }
445
}
446

            
447
/// Release storage held by an `ArtiRpcHandle`.
448
///
449
/// NOTE: This does not cancel the underlying request if it is still running.
450
/// To cancel a request, use `arti_rpc_conn_cancel_handle`.
451
#[allow(clippy::missing_safety_doc)]
452
#[no_mangle]
453
pub unsafe extern "C" fn arti_rpc_handle_free(handle: *mut ArtiRpcHandle) {
454
    ffi_body_raw!(
455
        {
456
            let handle: Option<Box<ArtiRpcHandle>> [in_ptr_consume_opt];
457
        } in {
458
            drop(handle);
459
            // Safety: Return value is (); trivially safe.
460
            ()
461
        }
462
    );
463
}
464
/// Free a string returned by the Arti RPC API.
465
#[allow(clippy::missing_safety_doc)]
466
#[no_mangle]
467
pub unsafe extern "C" fn arti_rpc_str_free(string: *mut ArtiRpcStr) {
468
    ffi_body_raw!(
469
        {
470
            let string: Option<Box<ArtiRpcStr>> [in_ptr_consume_opt];
471
        } in {
472
            drop(string);
473
            // Safety: Return value is (); trivially safe.
474
            ()
475
        }
476
    );
477
}
478

            
479
/// Return a const pointer to the underlying nul-terminated string from an `ArtiRpcStr`.
480
///
481
/// The resulting string is guaranteed to be valid UTF-8.
482
///
483
/// (Returns NULL if the input is NULL.)
484
///
485
/// # Correctness requirements
486
///
487
/// The resulting string pointer is valid only for as long as the input `string` is not freed.
488
#[allow(clippy::missing_safety_doc)]
489
#[no_mangle]
490
pub unsafe extern "C" fn arti_rpc_str_get(string: *const ArtiRpcStr) -> *const c_char {
491
    ffi_body_raw!(
492
        {
493
            let string: Option<&ArtiRpcStr> [in_ptr_opt];
494
        } in {
495
            // Safety: returned pointer is null, or semantically borrowed from `string`.
496
            // It is only null if `string` was null.
497
            // The caller is not allowed to modify it.
498
            match string {
499
                Some(s) => s.as_ptr(),
500
                None => std::ptr::null(),
501
            }
502

            
503
        }
504
    )
505
}
506

            
507
/// Close and free an open Arti RPC connection.
508
#[allow(clippy::missing_safety_doc)]
509
#[no_mangle]
510
pub unsafe extern "C" fn arti_rpc_conn_free(rpc_conn: *mut ArtiRpcConn) {
511
    ffi_body_raw!(
512
        {
513
            let rpc_conn: Option<Box<ArtiRpcConn>> [in_ptr_consume_opt];
514
        } in {
515
            drop(rpc_conn);
516
            // Safety: Return value is (); trivially safe.
517
            ()
518

            
519
        }
520
    );
521
}
522

            
523
/// Try to open an anonymized data stream over Arti.
524
///
525
/// Use the proxy information associated with `rpc_conn` to make the stream,
526
/// and store the resulting fd (or `SOCKET` on Windows) into `*socket_out`.
527
///
528
/// The stream will target the address `hostname`:`port`.
529
///
530
/// If `on_object` is provided, it is an `ObjectId` for client-like object
531
/// (such as a Session or a Client)
532
/// that should be used to make the stream.
533
///
534
/// The resulting stream will be configured
535
/// not to share a circuit with any other stream
536
/// having a different `isolation`.
537
/// (If your application doesn't care about isolating its streams from one another,
538
/// it is acceptable to leave `isolation` as an empty string.)
539
///
540
/// If `stream_id_out` is provided,
541
/// the resulting stream will have an identifier within the RPC system,
542
/// so that you can run other RPC commands on it.
543
///
544
/// On success, return `ARTI_RPC_STATUS_SUCCESS`.
545
/// Otherwise return some other status code, set `*socket_out` to -1
546
/// (or `INVALID_SOCKET` on Windows),
547
/// and set `*error_out` (if provided) to a newly allocated error object.
548
///
549
/// # Caveats
550
///
551
/// When possible, use a hostname rather than an IP address.
552
/// If you *must* use an IP address, make sure that you have not gotten it
553
/// by a non-anonymous DNS lookup.
554
/// (Calling `gethostname()` or `getaddrinfo()` directly
555
/// would lose anonymity: they inform the user's DNS server,
556
/// and possibly many other parties, about the target address
557
/// you are trying to visit.)
558
///
559
/// The resulting socket will actually be a TCP connection to Arti,
560
/// not directly to your destination.
561
/// Therefore, passing it to functions like `getpeername()`
562
/// may give unexpected results.
563
///
564
/// If `stream_id_out` is provided,
565
/// the caller is responsible for releasing the ObjectId;
566
/// Arti will not deallocate it even when the stream is closed.
567
///
568
/// # Ownership
569
///
570
/// The caller is responsible for making sure that
571
/// `*stream_id_out` and `*error_out`, if set,
572
/// are eventually freed.
573
///
574
/// The caller is responsible for making sure that `*socket_out`, if set,
575
/// is eventually closed.
576
#[allow(clippy::missing_safety_doc)]
577
#[no_mangle]
578
pub unsafe extern "C" fn arti_rpc_conn_open_stream(
579
    rpc_conn: *const ArtiRpcConn,
580
    hostname: *const c_char,
581
    port: c_int,
582
    on_object: *const c_char,
583
    isolation: *const c_char,
584
    socket_out: *mut ArtiRpcRawSocket,
585
    stream_id_out: *mut *mut ArtiRpcStr,
586
    error_out: *mut *mut ArtiRpcError,
587
) -> ArtiRpcStatus {
588
    ffi_body_with_err! {
589
        {
590
            let rpc_conn: Option<&ArtiRpcConn> [in_ptr_opt];
591
            let on_object: Option<&str> [in_str_opt];
592
            let hostname: Option<&str> [in_str_opt];
593
            let isolation: Option<&str> [in_str_opt];
594
            let socket_out: Option<OutSocketOwned<'_>> [out_socket_owned_opt];
595
            let stream_id_out: Option<OutPtr<ArtiRpcStr>> [out_ptr_opt];
596
            err error_out: Option<OutPtr<ArtiRpcError>>;
597
        } in {
598
            let rpc_conn = rpc_conn.ok_or(InvalidInput::NullPointer)?;
599
            let hostname = hostname.ok_or(InvalidInput::NullPointer)?;
600
            let socket_out = socket_out.ok_or(InvalidInput::NullPointer)?;
601
            let isolation = isolation.ok_or(InvalidInput::NullPointer)?;
602

            
603
            let port: u16 = port.try_into().map_err(|_| InvalidInput::BadPort)?;
604
            if port == 0 {
605
                return Err(InvalidInput::BadPort.into());
606
            }
607

            
608
            let on_object = on_object.map(|o| ObjectId::try_from(o.to_owned()))
609
                .transpose()
610
                .expect("C string somehow contained NUL.");
611

            
612
            let stream = match stream_id_out {
613
                Some(stream_id_out) => {
614
                    let (stream_id, stream) = rpc_conn.open_stream_as_object(
615
                        on_object.as_ref(),
616
                        (hostname, port),
617
                        isolation)?;
618
                    stream_id_out.write_value_boxed(stream_id.into());
619
                    stream
620
                }
621
                None => {
622
                    rpc_conn.open_stream(on_object.as_ref(), (hostname, port), isolation)?
623
                }
624
            };
625

            
626
            // We call this last so that the stream will definitely be converted to an fd, or
627
            // dropped.
628
            socket_out.write_socket(stream);
629
        }
630
    }
631
}