From 9c6b5821ac04799697c163cb296d9734b4354979 Mon Sep 17 00:00:00 2001 From: danielbotros Date: Tue, 28 Apr 2026 13:44:27 -0400 Subject: [PATCH 1/6] [RSDK-13658] Plumb force_relay/force_p2p/turn_uri through C FFI Exposes the DialBuilder methods added in #170 (force_relay, force_p2p, turn_uri) through viam_dial so non-Rust SDKs can drive ICE relay/P2P testing and TURN-server filtering. The deprecated `dial` keeps its 7-arg ABI by trampolining into viam_dial with `false, false, NULL` defaults. BREAKING (C ABI): viam_dial now takes 10 args instead of 7. Any non-Rust consumer linking against viam_dial must rebuild against the new header. Consumers still using the deprecated `dial` are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/ffi/dial_ffi.rs | 61 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/src/ffi/dial_ffi.rs b/src/ffi/dial_ffi.rs index 762b8e5..13dd2cc 100644 --- a/src/ffi/dial_ffi.rs +++ b/src/ffi/dial_ffi.rs @@ -81,14 +81,16 @@ fn dial_without_cred( uri: String, allow_insec: bool, disable_webrtc: bool, + force_relay: bool, + force_p2p: bool, + turn_uri: Option, ) -> Result> { let c = DialOptions::builder().uri(&uri).without_credentials(); - let c = if disable_webrtc { - c.disable_webrtc() - } else { - c - }; + let c = if disable_webrtc { c.disable_webrtc() } else { c }; let c = if allow_insec { c.allow_downgrade() } else { c }; + let c = if force_relay { c.force_relay() } else { c }; + let c = if force_p2p { c.force_p2p() } else { c }; + let c = if let Some(u) = turn_uri { c.turn_uri(u) } else { c }; Ok(c) } @@ -99,15 +101,17 @@ fn dial_with_cred( payload: &str, allow_insec: bool, disable_webrtc: bool, + force_relay: bool, + force_p2p: bool, + turn_uri: Option, ) -> Result> { 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() - } else { - c - }; + let c = if disable_webrtc { c.disable_webrtc() } else { c }; let c = if allow_insec { c.allow_downgrade() } else { c }; + let c = if force_relay { c.force_relay() } else { c }; + let c = if force_p2p { c.force_p2p() } else { c }; + let c = if let Some(u) = turn_uri { c.turn_uri(u) } else { c }; Ok(c) } @@ -123,6 +127,10 @@ fn dial_with_cred( /// * `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 +/// * `c_force_relay` a bool, set to true to force ICE relay-only policy (TURN candidates only) +/// * `c_force_p2p` a bool, set to true to strip TURN servers and force host/reflexive candidates only +/// * `c_turn_uri` a C-style string with a TURN URI filter (e.g. "turns::443?transport=tcp"); set to +/// NULL to use all TURN servers. An empty host matches any TURN provider. /// * `rt_ptr` a pointer to a rust runtime previously obtained with init_rust_runtime #[no_mangle] pub unsafe extern "C" fn viam_dial( @@ -132,6 +140,9 @@ pub unsafe extern "C" fn viam_dial( c_payload: *const c_char, c_allow_insec: bool, c_timeout: f32, + c_force_relay: bool, + c_force_p2p: bool, + c_turn_uri: *const c_char, rt_ptr: Option<&mut DialFfi>, ) -> *mut c_char { let uri = { @@ -215,6 +226,20 @@ pub unsafe extern "C" fn viam_dial( }; let timeout_duration = Duration::from_secs_f32(c_timeout); + let turn_uri_opt = { + match c_turn_uri.is_null() { + true => None, + false => match CStr::from_ptr(c_turn_uri).to_str() { + Ok(s) if !s.is_empty() => Some(s.to_string()), + Ok(_) => None, + Err(e) => { + log::error!("Error parsing turn_uri string: {:?}", e); + return ptr::null_mut(); + } + }, + } + }; + let (server, channel) = match runtime.block_on(async move { let channel = match (r#type, payload) { (Some(t), Some(p)) => { @@ -227,6 +252,9 @@ pub unsafe extern "C" fn viam_dial( p.to_str()?, allow_insec, disable_webrtc, + c_force_relay, + c_force_p2p, + turn_uri_opt, )? .connect(), ) @@ -235,7 +263,15 @@ pub unsafe extern "C" fn viam_dial( (None, None) => { timeout( timeout_duration, - dial_without_cred(uri_str, allow_insec, disable_webrtc)?.connect(), + dial_without_cred( + uri_str, + allow_insec, + disable_webrtc, + c_force_relay, + c_force_p2p, + turn_uri_opt, + )? + .connect(), ) .await? } @@ -297,6 +333,9 @@ pub unsafe extern "C" fn dial( c_payload, c_allow_insec, c_timeout, + false, + false, + ptr::null(), rt_ptr, ) } From 3107b20a5233bb01e2303e38bc899bd0ce8eee98 Mon Sep 17 00:00:00 2001 From: danielbotros Date: Mon, 4 May 2026 13:33:15 -0400 Subject: [PATCH 2/6] Update comment --- src/ffi/dial_ffi.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ffi/dial_ffi.rs b/src/ffi/dial_ffi.rs index 13dd2cc..6303ce2 100644 --- a/src/ffi/dial_ffi.rs +++ b/src/ffi/dial_ffi.rs @@ -129,8 +129,9 @@ fn dial_with_cred( /// * `c_timeout` a float, set how many seconds we should try to dial before timing out /// * `c_force_relay` a bool, set to true to force ICE relay-only policy (TURN candidates only) /// * `c_force_p2p` a bool, set to true to strip TURN servers and force host/reflexive candidates only -/// * `c_turn_uri` a C-style string with a TURN URI filter (e.g. "turns::443?transport=tcp"); set to -/// NULL to use all TURN servers. An empty host matches any TURN provider. +/// * `c_turn_uri` a C-style string with a TURN URI filter (e.g. "turn:turn.viam.com:443"); set to +/// NULL or empty to use all TURN servers. The filter matches TURN URLs by scheme, host, port, +/// and transport (transport defaults to "udp" if unspecified). /// * `rt_ptr` a pointer to a rust runtime previously obtained with init_rust_runtime #[no_mangle] pub unsafe extern "C" fn viam_dial( From e1fe931b312cd383ffcd8e9d82df1ac4e5f7f560 Mon Sep 17 00:00:00 2001 From: danielbotros Date: Tue, 5 May 2026 14:03:08 -0400 Subject: [PATCH 3/6] Generate backward compat viam_dial header for cpp --- cbindgen.toml | 20 ++++++++++++++++++++ src/ffi/dial_ffi.rs | 10 +++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/cbindgen.toml b/cbindgen.toml index f84afca..ad043e0 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -7,6 +7,26 @@ pragma_once = true autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" cpp_compat = true +# C++-only re-declaration that adds default arguments to viam_dial's trailing +# params, letting C++ callers (e.g. the C++ SDK) keep using the original 7-arg +# signature. C callers must pass all arguments. Keep this parameter list in +# sync with `viam_dial` in src/ffi/dial_ffi.rs — drift will surface as a C++ +# redeclaration mismatch at the caller's compile time. +trailer = ''' +#ifdef __cplusplus +extern "C" char *viam_dial(const char *c_uri, + const char *c_entity, + const char *c_type, + const char *c_payload, + bool c_allow_insec, + float c_timeout, + struct viam_dial_ffi *rt_ptr, + bool c_force_relay = false, + bool c_force_p2p = false, + const char *c_turn_uri = nullptr); +#endif +''' + ############################ Code Style Options ################################ diff --git a/src/ffi/dial_ffi.rs b/src/ffi/dial_ffi.rs index 6303ce2..513ed07 100644 --- a/src/ffi/dial_ffi.rs +++ b/src/ffi/dial_ffi.rs @@ -127,12 +127,16 @@ fn dial_with_cred( /// * `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 +// +// NOTE: C++ SDK's existing 7-arg call site of viam_dial relies on `rt_ptr` keeping its original trailing position; the following +// args are appended so cbindgen.toml's C++ default-argument trailer can fill them in for 7-arg callers. +// /// * `c_force_relay` a bool, set to true to force ICE relay-only policy (TURN candidates only) /// * `c_force_p2p` a bool, set to true to strip TURN servers and force host/reflexive candidates only /// * `c_turn_uri` a C-style string with a TURN URI filter (e.g. "turn:turn.viam.com:443"); set to /// NULL or empty to use all TURN servers. The filter matches TURN URLs by scheme, host, port, /// and transport (transport defaults to "udp" if unspecified). -/// * `rt_ptr` a pointer to a rust runtime previously obtained with init_rust_runtime #[no_mangle] pub unsafe extern "C" fn viam_dial( c_uri: *const c_char, @@ -141,10 +145,10 @@ pub unsafe extern "C" fn viam_dial( c_payload: *const c_char, c_allow_insec: bool, c_timeout: f32, + rt_ptr: Option<&mut DialFfi>, c_force_relay: bool, c_force_p2p: bool, c_turn_uri: *const c_char, - rt_ptr: Option<&mut DialFfi>, ) -> *mut c_char { let uri = { if c_uri.is_null() { @@ -334,10 +338,10 @@ pub unsafe extern "C" fn dial( c_payload, c_allow_insec, c_timeout, + rt_ptr, false, false, ptr::null(), - rt_ptr, ) } From 6c2208234dd358dc616c9fa0290ea7f63251907f Mon Sep 17 00:00:00 2001 From: danielbotros Date: Tue, 12 May 2026 15:48:57 -0400 Subject: [PATCH 4/6] Add new dial with options function; generate void* header --- cbindgen.toml | 21 +--- src/ffi/dial_ffi.rs | 266 ++++++++++++++++++++++++++++++++------------ 2 files changed, 197 insertions(+), 90 deletions(-) diff --git a/cbindgen.toml b/cbindgen.toml index ad043e0..e816d5c 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -7,26 +7,6 @@ pragma_once = true autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" cpp_compat = true -# C++-only re-declaration that adds default arguments to viam_dial's trailing -# params, letting C++ callers (e.g. the C++ SDK) keep using the original 7-arg -# signature. C callers must pass all arguments. Keep this parameter list in -# sync with `viam_dial` in src/ffi/dial_ffi.rs — drift will surface as a C++ -# redeclaration mismatch at the caller's compile time. -trailer = ''' -#ifdef __cplusplus -extern "C" char *viam_dial(const char *c_uri, - const char *c_entity, - const char *c_type, - const char *c_payload, - bool c_allow_insec, - float c_timeout, - struct viam_dial_ffi *rt_ptr, - bool c_force_relay = false, - bool c_force_p2p = false, - const char *c_turn_uri = nullptr); -#endif -''' - ############################ Code Style Options ################################ @@ -73,6 +53,7 @@ rename_types = "snake_case" [fn] deprecated = "/// @deprecated please use `viam_`-prefixed function instead" +deprecated_with_note = "/// @deprecated {}" [struct] rename_fields = "SnakeCase" diff --git a/src/ffi/dial_ffi.rs b/src/ffi/dial_ffi.rs index 513ed07..3c13c82 100644 --- a/src/ffi/dial_ffi.rs +++ b/src/ffi/dial_ffi.rs @@ -14,7 +14,7 @@ use tracing::Level; 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; @@ -63,6 +63,18 @@ impl DialFfi { } } } + +// Internal-only options bag 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, +} + /// 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] @@ -77,20 +89,97 @@ pub extern "C" fn init_rust_runtime() -> Box { 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::::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 candidates only — no relay). +/// +/// # 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, - force_relay: bool, - force_p2p: bool, - turn_uri: Option, + opts: &DialOpts, ) -> Result> { let c = DialOptions::builder().uri(&uri).without_credentials(); let c = if disable_webrtc { c.disable_webrtc() } else { c }; let c = if allow_insec { c.allow_downgrade() } else { c }; - let c = if force_relay { c.force_relay() } else { c }; - let c = if force_p2p { c.force_p2p() } else { c }; - let c = if let Some(u) = turn_uri { c.turn_uri(u) } 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 + }; Ok(c) } @@ -101,44 +190,25 @@ fn dial_with_cred( payload: &str, allow_insec: bool, disable_webrtc: bool, - force_relay: bool, - force_p2p: bool, - turn_uri: Option, + opts: &DialOpts, ) -> Result> { 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() } else { c }; let c = if allow_insec { c.allow_downgrade() } else { c }; - let c = if force_relay { c.force_relay() } else { c }; - let c = if force_p2p { c.force_p2p() } else { c }; - let c = if let Some(u) = turn_uri { c.turn_uri(u) } 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 + }; 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 -// -// NOTE: C++ SDK's existing 7-arg call site of viam_dial relies on `rt_ptr` keeping its original trailing position; the following -// args are appended so cbindgen.toml's C++ default-argument trailer can fill them in for 7-arg callers. -// -/// * `c_force_relay` a bool, set to true to force ICE relay-only policy (TURN candidates only) -/// * `c_force_p2p` a bool, set to true to strip TURN servers and force host/reflexive candidates only -/// * `c_turn_uri` a C-style string with a TURN URI filter (e.g. "turn:turn.viam.com:443"); set to -/// NULL or empty to use all TURN servers. The filter matches TURN URLs by scheme, host, port, -/// and transport (transport defaults to "udp" if unspecified). -#[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, @@ -146,9 +216,7 @@ pub unsafe extern "C" fn viam_dial( c_allow_insec: bool, c_timeout: f32, rt_ptr: Option<&mut DialFfi>, - c_force_relay: bool, - c_force_p2p: bool, - c_turn_uri: *const c_char, + opts: &DialOpts, ) -> *mut c_char { let uri = { if c_uri.is_null() { @@ -231,20 +299,6 @@ pub unsafe extern "C" fn viam_dial( }; let timeout_duration = Duration::from_secs_f32(c_timeout); - let turn_uri_opt = { - match c_turn_uri.is_null() { - true => None, - false => match CStr::from_ptr(c_turn_uri).to_str() { - Ok(s) if !s.is_empty() => Some(s.to_string()), - Ok(_) => None, - Err(e) => { - log::error!("Error parsing turn_uri string: {:?}", e); - return ptr::null_mut(); - } - }, - } - }; - let (server, channel) = match runtime.block_on(async move { let channel = match (r#type, payload) { (Some(t), Some(p)) => { @@ -257,9 +311,7 @@ pub unsafe extern "C" fn viam_dial( p.to_str()?, allow_insec, disable_webrtc, - c_force_relay, - c_force_p2p, - turn_uri_opt, + opts, )? .connect(), ) @@ -268,15 +320,7 @@ pub unsafe extern "C" fn viam_dial( (None, None) => { timeout( timeout_duration, - dial_without_cred( - uri_str, - allow_insec, - disable_webrtc, - c_force_relay, - c_force_p2p, - turn_uri_opt, - )? - .connect(), + dial_without_cred(uri_str, allow_insec, disable_webrtc, opts)?.connect(), ) .await? } @@ -320,6 +364,90 @@ pub unsafe extern "C" fn viam_dial( 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`]. Must not be NULL; +/// NULL returns a NULL path. Caller retains ownership and must free it 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 { + if opts.is_null() { + log::error!("viam_dial_with_opts called with NULL opts; use viam_dial_opts_new()"); + return ptr::null_mut(); + } + let opts_ref: &DialOpts = &*(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( @@ -331,7 +459,7 @@ pub unsafe extern "C" fn dial( c_timeout: f32, rt_ptr: Option<&mut DialFfi>, ) -> *mut c_char { - viam_dial( + dial_impl( c_uri, c_entity, c_type, @@ -339,9 +467,7 @@ pub unsafe extern "C" fn dial( c_allow_insec, c_timeout, rt_ptr, - false, - false, - ptr::null(), + &DialOpts::default(), ) } From fc7af214b7ae873cac45c669f889d09b4a5a2388 Mon Sep 17 00:00:00 2001 From: danielbotros Date: Tue, 12 May 2026 16:54:01 -0400 Subject: [PATCH 5/6] Handle null opts --- src/ffi/dial_ffi.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ffi/dial_ffi.rs b/src/ffi/dial_ffi.rs index 3c13c82..139d4cc 100644 --- a/src/ffi/dial_ffi.rs +++ b/src/ffi/dial_ffi.rs @@ -417,8 +417,9 @@ pub unsafe extern "C" fn viam_dial( /// # 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`]. Must not be NULL; -/// NULL returns a NULL path. Caller retains ownership and must free it with +/// * `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( @@ -431,11 +432,13 @@ pub unsafe extern "C" fn viam_dial_with_opts( rt_ptr: Option<&mut DialFfi>, opts: *const c_void, ) -> *mut c_char { - if opts.is_null() { - log::error!("viam_dial_with_opts called with NULL opts; use viam_dial_opts_new()"); - return ptr::null_mut(); - } - let opts_ref: &DialOpts = &*(opts as *const DialOpts); + 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, From ba2ace70ee77153681fa3b2b70b9d4b244d6b035 Mon Sep 17 00:00:00 2001 From: danielbotros Date: Wed, 13 May 2026 10:41:04 -0400 Subject: [PATCH 6/6] Clean up comments --- src/ffi/dial_ffi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ffi/dial_ffi.rs b/src/ffi/dial_ffi.rs index 139d4cc..ca210a6 100644 --- a/src/ffi/dial_ffi.rs +++ b/src/ffi/dial_ffi.rs @@ -64,7 +64,7 @@ impl DialFfi { } } -// Internal-only options bag backing the opaque handle exposed across the FFI. +// 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. @@ -128,7 +128,7 @@ pub unsafe extern "C" fn viam_dial_opts_set_force_relay(opts: *mut c_void, value } /// Strip TURN servers from the ICE configuration so the connection must be -/// established peer-to-peer (host/srflx/prflx candidates only — no relay). +/// established peer-to-peer (host/srflx/prflx). /// /// # Safety /// `opts` must be a handle obtained from [`viam_dial_opts_new`]. NULL is a no-op.