arti_rpc_client_core/ffi.rs
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
6pub mod err;
7mod util;
8
9use err::{ArtiRpcError, InvalidInput};
10use std::ffi::{c_char, c_int};
11use std::sync::Mutex;
12use util::{
13 ffi_body_raw, ffi_body_with_err, OptOutPtrExt as _, OptOutValExt, OutPtr, OutSocketOwned,
14 OutVal,
15};
16
17use 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.
27pub 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`]
35pub 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`].
43pub 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.
50pub 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`.
57pub type ArtiRpcHandle = RequestHandle;
58
59/// The type of a message returned by an RPC request.
60pub type ArtiRpcResponseType = c_int;
61
62/// The type of an entry prepended to a connect point search path.
63pub 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)]
71pub struct ArtiRpcRawSocket(
72 #[cfg(windows)] std::os::windows::raw::SOCKET,
73 #[cfg(not(windows))] c_int,
74);
75
76impl 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]
102pub 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]
120pub 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`].
135pub 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`].
140pub 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`].
145pub 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]
164pub 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]
205pub 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]
238pub 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]
275pub 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]
316pub 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]
350pub 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.
373pub 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.
377pub 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.
382pub const ARTI_RPC_RESPONSE_TYPE_ERROR: ArtiRpcResponseType = 3;
383
384impl 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]
423pub 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]
453pub 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]
467pub 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]
490pub 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]
510pub 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]
578pub 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}