Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ rename_types = "snake_case"

[fn]
deprecated = "/// @deprecated please use `viam_`-prefixed function instead"
deprecated_with_note = "/// @deprecated {}"

[struct]
rename_fields = "SnakeCase"
Expand Down
221 changes: 197 additions & 24 deletions src/ffi/dial_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use crate::rpc::dial::{
DialBuilder, DialOptions, RPCCredentials, ViamChannel, WithCredentials, WithoutCredentials,
};
use libc::c_char;
use libc::{c_char, c_void};

use crate::proxy;
use hyper::Server;
Expand Down Expand Up @@ -63,6 +63,18 @@
}
}
}

// Internal-only options struct backing the opaque handle exposed across the FFI.
// Field additions here are ABI-additive: the C side only ever sees a `void *`
// (see `viam_dial_opts_new` etc. below), so new fields require new setters but
// never break the existing header.
#[derive(Default)]
struct DialOpts {
force_relay: bool,
force_p2p: bool,
turn_uri: Option<String>,
}

/// Initialize a tokio runtime to run a gRPC client/sever, user should call this function before trying to dial to a Robot
/// Returns a pointer to a [`DialFfi`]
#[no_mangle]
Expand All @@ -77,18 +89,97 @@
viam_init_rust_runtime()
}

/// Allocate a fresh dial options handle with default values
/// (force_relay=false, force_p2p=false, turn_uri=NULL).
///
/// The returned pointer is opaque on the C side (`void *`) and must be freed
/// with [`viam_dial_opts_free`]. Pass it to [`viam_dial_with_opts`] after
/// mutating via the `viam_dial_opts_set_*` setters.
///
/// Returns NULL on allocation failure.
#[no_mangle]
pub extern "C" fn viam_dial_opts_new() -> *mut c_void {
Box::into_raw(Box::<DialOpts>::default()) as *mut c_void
}

/// Free a dial options handle previously returned by [`viam_dial_opts_new`].
///
/// # Safety
/// The pointer must come from [`viam_dial_opts_new`] and must not be freed
/// more than once. NULL is a no-op.
#[no_mangle]
pub unsafe extern "C" fn viam_dial_opts_free(opts: *mut c_void) {
if opts.is_null() {
return;
}
drop(Box::from_raw(opts as *mut DialOpts));
}

/// Force the ICE transport policy to relay-only (TURN candidates only).
///
/// # Safety
/// `opts` must be a handle obtained from [`viam_dial_opts_new`]. NULL is a no-op.
#[no_mangle]
pub unsafe extern "C" fn viam_dial_opts_set_force_relay(opts: *mut c_void, value: bool) {
if opts.is_null() {
return;
}
(*(opts as *mut DialOpts)).force_relay = value;
}

/// Strip TURN servers from the ICE configuration so the connection must be
/// established peer-to-peer (host/srflx/prflx).
///
/// # Safety
/// `opts` must be a handle obtained from [`viam_dial_opts_new`]. NULL is a no-op.
#[no_mangle]
pub unsafe extern "C" fn viam_dial_opts_set_force_p2p(opts: *mut c_void, value: bool) {
if opts.is_null() {
return;
}
(*(opts as *mut DialOpts)).force_p2p = value;
}

/// Set a TURN URI filter (e.g. `"turn:turn.viam.com:443"`). When set, only TURN
/// servers matching scheme/host/port/transport (transport defaults to "udp")
/// are used. Pass NULL or an empty string to clear and use all TURN servers.
/// The string is copied; the caller may free their copy after this call returns.
///
/// # Safety
/// `opts` must be a handle obtained from [`viam_dial_opts_new`]. NULL is a no-op.
#[no_mangle]
pub unsafe extern "C" fn viam_dial_opts_set_turn_uri(opts: *mut c_void, value: *const c_char) {
if opts.is_null() {
return;
}
let opts = &mut *(opts as *mut DialOpts);
if value.is_null() {
opts.turn_uri = None;
return;
}
match CStr::from_ptr(value).to_str() {
Ok(s) if !s.is_empty() => opts.turn_uri = Some(s.to_string()),
Ok(_) => opts.turn_uri = None,
Err(e) => log::error!("invalid turn_uri string: {e:?}"),
}
}

fn dial_without_cred(
uri: String,
allow_insec: bool,
disable_webrtc: bool,
opts: &DialOpts,
) -> Result<DialBuilder<WithoutCredentials>> {
let c = DialOptions::builder().uri(&uri).without_credentials();
let c = if disable_webrtc {
c.disable_webrtc()
let c = if disable_webrtc { c.disable_webrtc() } else { c };
let c = if allow_insec { c.allow_downgrade() } else { c };
let c = if opts.force_relay { c.force_relay() } else { c };
let c = if opts.force_p2p { c.force_p2p() } else { c };
let c = if let Some(u) = opts.turn_uri.clone() {
c.turn_uri(u)
} else {
c
};
let c = if allow_insec { c.allow_downgrade() } else { c };
Ok(c)
}

Expand All @@ -99,41 +190,34 @@
payload: &str,
allow_insec: bool,
disable_webrtc: bool,
opts: &DialOpts,
) -> Result<DialBuilder<WithCredentials>> {
let creds = RPCCredentials::new(entity, String::from(r#type), String::from(payload));
let c = DialOptions::builder().uri(&uri).with_credentials(creds);
let c = if disable_webrtc {
c.disable_webrtc()
let c = if disable_webrtc { c.disable_webrtc() } else { c };
let c = if allow_insec { c.allow_downgrade() } else { c };
let c = if opts.force_relay { c.force_relay() } else { c };
let c = if opts.force_p2p { c.force_p2p() } else { c };
let c = if let Some(u) = opts.turn_uri.clone() {
c.turn_uri(u)
} else {
c
};
let c = if allow_insec { c.allow_downgrade() } else { c };
Ok(c)
}

/// Returns a path to a proxy to a robot
/// # Safety
///
/// This function must be called from another language. See [`dial`](mod@crate::rpc::dial) for dial from rust
/// The function returns a path to a proxy as a [`c_char`], the string should be freed with free_string when not needed anymore.
/// When falling to dial it will return a NULL pointer
/// # Arguments
/// * `c_uri` a C-style string representing the address of robot you want to connect to
/// * `c_type` a C-style string representing the type of robot's secret you want to use, set to NULL if you don't need authentication
/// * `c_payload` a C-style string that is the robot's secret, set to NULL if you don't need authentication
/// * `c_allow_insecure` a bool, set to true when allowing insecure connection to your robot
/// * `c_timeout` a float, set how many seconds we should try to dial before timing out
/// * `rt_ptr` a pointer to a rust runtime previously obtained with init_rust_runtime
#[no_mangle]
pub unsafe extern "C" fn viam_dial(
// Shared implementation behind both `viam_dial` (default opts) and
// `viam_dial_with_opts` (caller-supplied opts).
unsafe fn dial_impl(
c_uri: *const c_char,
c_entity: *const c_char,
c_type: *const c_char,
c_payload: *const c_char,
c_allow_insec: bool,
c_timeout: f32,
rt_ptr: Option<&mut DialFfi>,
opts: &DialOpts,
) -> *mut c_char {

Check warning on line 220 in src/ffi/dial_ffi.rs

View workflow job for this annotation

GitHub Actions / clippy

this function has too many arguments (8/7)

warning: this function has too many arguments (8/7) --> src/ffi/dial_ffi.rs:211:1 | 211 | / unsafe fn dial_impl( 212 | | c_uri: *const c_char, 213 | | c_entity: *const c_char, 214 | | c_type: *const c_char, ... | 219 | | opts: &DialOpts, 220 | | ) -> *mut c_char { | |________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#too_many_arguments = note: `#[warn(clippy::too_many_arguments)]` on by default
let uri = {
if c_uri.is_null() {
return ptr::null_mut();
Expand Down Expand Up @@ -227,6 +311,7 @@
p.to_str()?,
allow_insec,
disable_webrtc,
opts,
)?
.connect(),
)
Expand All @@ -235,7 +320,7 @@
(None, None) => {
timeout(
timeout_duration,
dial_without_cred(uri_str, allow_insec, disable_webrtc)?.connect(),
dial_without_cred(uri_str, allow_insec, disable_webrtc, opts)?.connect(),
)
.await?
}
Expand Down Expand Up @@ -272,32 +357,120 @@
let server = server.with_graceful_shutdown(async {
rx.await.ok();
});
let _ = runtime.spawn(async {
let _ = server.await;
});

Check warning on line 362 in src/ffi/dial_ffi.rs

View workflow job for this annotation

GitHub Actions / clippy

non-binding `let` on a future

warning: non-binding `let` on a future --> src/ffi/dial_ffi.rs:360:5 | 360 | / let _ = runtime.spawn(async { 361 | | let _ = server.await; 362 | | }); | |_______^ | = help: consider awaiting the future or dropping explicitly with `std::mem::drop` = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#let_underscore_future = note: `#[warn(clippy::let_underscore_future)]` on by default
ctx.push_signal(tx);
path.into_raw()
}

/// Returns a path to a proxy to a robot, using default dial options
/// (no force_relay, no force_p2p, no turn_uri filter).
///
/// # Safety
///
/// This function must be called from another language. See [`dial`](mod@crate::rpc::dial) for dial from rust
/// The function returns a path to a proxy as a [`c_char`], the string should be freed with free_string when not needed anymore.
/// When falling to dial it will return a NULL pointer
/// # Arguments
/// * `c_uri` a C-style string representing the address of robot you want to connect to
/// * `c_type` a C-style string representing the type of robot's secret you want to use, set to NULL if you don't need authentication
/// * `c_payload` a C-style string that is the robot's secret, set to NULL if you don't need authentication
/// * `c_allow_insecure` a bool, set to true when allowing insecure connection to your robot
/// * `c_timeout` a float, set how many seconds we should try to dial before timing out
/// * `rt_ptr` a pointer to a rust runtime previously obtained with init_rust_runtime
#[no_mangle]
#[deprecated(note = "please use viam_dial_with_opts instead")]
pub unsafe extern "C" fn viam_dial(
c_uri: *const c_char,
c_entity: *const c_char,
c_type: *const c_char,
c_payload: *const c_char,
c_allow_insec: bool,
c_timeout: f32,
rt_ptr: Option<&mut DialFfi>,
) -> *mut c_char {
dial_impl(
c_uri,
c_entity,
c_type,
c_payload,
c_allow_insec,
c_timeout,
rt_ptr,
&DialOpts::default(),
)
}

/// Returns a path to a proxy to a robot, using caller-supplied dial options.
///
/// This is the permanent successor to [`viam_dial`]. New dial options can be
/// added in the future by adding new `viam_dial_opts_set_*` setters without
/// changing this function's signature or breaking the C ABI.
///
/// # Safety
///
/// This function must be called from another language. The function returns
/// a path to a proxy as a [`c_char`]; the string should be freed with
/// [`viam_free_string`] when not needed anymore. On failure it returns NULL.
///
/// # Arguments
/// * `c_uri`, `c_entity`, `c_type`, `c_payload`, `c_allow_insec`, `c_timeout`,
/// `rt_ptr` — same as [`viam_dial`].
/// * `opts` — an opaque handle from [`viam_dial_opts_new`], or NULL to use
/// default options (no force_relay, no force_p2p, no turn_uri filter). When
/// non-NULL, the caller retains ownership and must free the handle with
/// [`viam_dial_opts_free`] after this call returns.
#[no_mangle]
pub unsafe extern "C" fn viam_dial_with_opts(
c_uri: *const c_char,
c_entity: *const c_char,
c_type: *const c_char,
c_payload: *const c_char,
c_allow_insec: bool,
c_timeout: f32,
rt_ptr: Option<&mut DialFfi>,
opts: *const c_void,
) -> *mut c_char {
let default_opts;
let opts_ref: &DialOpts = if opts.is_null() {
default_opts = DialOpts::default();
&default_opts
} else {
&*(opts as *const DialOpts)
};
dial_impl(
c_uri,
c_entity,
c_type,
c_payload,
c_allow_insec,
c_timeout,
rt_ptr,
opts_ref,
)
}

#[no_mangle]
#[deprecated]
pub unsafe extern "C" fn dial(
c_uri: *const c_char,
c_entity: *const c_char,
c_type: *const c_char,
c_payload: *const c_char,
c_allow_insec: bool,
c_timeout: f32,
rt_ptr: Option<&mut DialFfi>,
) -> *mut c_char {

Check warning on line 464 in src/ffi/dial_ffi.rs

View workflow job for this annotation

GitHub Actions / clippy

unsafe function's docs are missing a `# Safety` section

warning: unsafe function's docs are missing a `# Safety` section --> src/ffi/dial_ffi.rs:456:1 | 456 | / pub unsafe extern "C" fn dial( 457 | | c_uri: *const c_char, 458 | | c_entity: *const c_char, 459 | | c_type: *const c_char, ... | 463 | | rt_ptr: Option<&mut DialFfi>, 464 | | ) -> *mut c_char { | |________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#missing_safety_doc = note: `#[warn(clippy::missing_safety_doc)]` on by default
viam_dial(
dial_impl(
c_uri,
c_entity,
c_type,
c_payload,
c_allow_insec,
c_timeout,
rt_ptr,
&DialOpts::default(),
)
}

Expand All @@ -318,7 +491,7 @@

#[no_mangle]
#[deprecated]
pub unsafe extern "C" fn free_string(s: *mut c_char) {

Check warning on line 494 in src/ffi/dial_ffi.rs

View workflow job for this annotation

GitHub Actions / clippy

unsafe function's docs are missing a `# Safety` section

warning: unsafe function's docs are missing a `# Safety` section --> src/ffi/dial_ffi.rs:494:1 | 494 | pub unsafe extern "C" fn free_string(s: *mut c_char) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#missing_safety_doc
viam_free_string(s)
}

Expand Down
Loading