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
4 changes: 2 additions & 2 deletions dash-spv-ffi/FFI_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Functions: 27
| Function | Description | Module |
|----------|-------------|--------|
| `dash_spv_ffi_client_update_config` | Update the running client's configuration | client |
| `dash_spv_ffi_config_add_peer` | Adds a peer address to the configuration Accepts either a full socket... | config |
| `dash_spv_ffi_config_add_peer` | Adds a peer address to the configuration Accepts socket addresses with or... | config |
| `dash_spv_ffi_config_destroy` | Destroys an FFIClientConfig and frees its memory # Safety - `config` must... | config |
| `dash_spv_ffi_config_get_data_dir` | Gets the data directory path from the configuration # Safety - `config`... | config |
| `dash_spv_ffi_config_get_mempool_strategy` | Gets the mempool synchronization strategy # Safety - `config` must be a... | config |
Expand Down Expand Up @@ -254,7 +254,7 @@ dash_spv_ffi_config_add_peer(config: *mut FFIClientConfig, addr: *const c_char,)
```

**Description:**
Adds a peer address to the configuration Accepts either a full socket address (e.g., `192.168.1.1:9999` or `[::1]:19999`) or an IP-only string (e.g., "127.0.0.1" or "2001:db8::1"). When an IP-only string is given, the default P2P port for the configured network is used. # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `addr` must be a valid null-terminated C string containing a socket address or IP-only string - The caller must ensure both pointers remain valid for the duration of this call
Adds a peer address to the configuration Accepts socket addresses with or without port. When no port is specified, the default P2P port for the configured network is used. Supported formats: - IP with port: `192.168.1.1:9999`, `[::1]:19999` - IP without port: `127.0.0.1`, `2001:db8::1` - Hostname with port: `node.example.com:9999` - Hostname without port: `node.example.com` # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `addr` must be a valid null-terminated C string containing a socket address or IP-only string - The caller must ensure both pointers remain valid for the duration of this call

**Safety:**
- `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `addr` must be a valid null-terminated C string containing a socket address or IP-only string - The caller must ensure both pointers remain valid for the duration of this call
Expand Down
11 changes: 8 additions & 3 deletions dash-spv-ffi/include/dash_spv_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -609,9 +609,14 @@ int32_t dash_spv_ffi_config_set_max_peers(struct FFIClientConfig *config,
/**
* Adds a peer address to the configuration
*
* Accepts either a full socket address (e.g., `192.168.1.1:9999` or `[::1]:19999`)
* or an IP-only string (e.g., "127.0.0.1" or "2001:db8::1"). When an IP-only
* string is given, the default P2P port for the configured network is used.
* Accepts socket addresses with or without port. When no port is specified,
* the default P2P port for the configured network is used.
*
* Supported formats:
* - IP with port: `192.168.1.1:9999`, `[::1]:19999`
* - IP without port: `127.0.0.1`, `2001:db8::1`
* - Hostname with port: `node.example.com:9999`
* - Hostname without port: `node.example.com`
*
* # Safety
* - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet
Expand Down
33 changes: 19 additions & 14 deletions dash-spv-ffi/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,14 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_max_peers(

/// Adds a peer address to the configuration
///
/// Accepts either a full socket address (e.g., `192.168.1.1:9999` or `[::1]:19999`)
/// or an IP-only string (e.g., "127.0.0.1" or "2001:db8::1"). When an IP-only
/// string is given, the default P2P port for the configured network is used.
/// Accepts socket addresses with or without port. When no port is specified,
/// the default P2P port for the configured network is used.
///
/// Supported formats:
/// - IP with port: `192.168.1.1:9999`, `[::1]:19999`
/// - IP without port: `127.0.0.1`, `2001:db8::1`
/// - Hostname with port: `node.example.com:9999`
/// - Hostname without port: `node.example.com`
///
/// # Safety
/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet
Expand Down Expand Up @@ -158,26 +163,26 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer(
}
};

// 1) Try parsing as full SocketAddr first (handles IPv6 [::1]:port forms)
if let Ok(sock) = addr_str.parse::<SocketAddr>() {
cfg.peers.push(sock);
return FFIErrorCode::Success as i32;
}

// 2) If that fails, try parsing as bare IP address and apply default port
// Try parsing as bare IP address and apply default port
if let Ok(ip) = addr_str.parse::<IpAddr>() {
let sock = SocketAddr::new(ip, default_port);
cfg.peers.push(sock);
return FFIErrorCode::Success as i32;
}

// 3) Optionally attempt DNS name with explicit port only; if no port, reject
if !addr_str.contains(':') {
set_last_error("Missing port for hostname; supply 'host:port' or IP only");
// If not, must be a hostname - reject empty or missing hostname
if addr_str.is_empty() || addr_str.starts_with(':') {
set_last_error("Empty or missing hostname");
return FFIErrorCode::InvalidArgument as i32;
}

match addr_str.to_socket_addrs() {
let addr_with_port = if addr_str.contains(':') {
addr_str.to_string()
} else {
format!("{}:{}", addr_str, default_port)
};

match addr_with_port.to_socket_addrs() {
Ok(mut iter) => match iter.next() {
Some(sock) => {
cfg.peers.push(sock);
Expand Down
30 changes: 19 additions & 11 deletions dash-spv-ffi/tests/unit/test_configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,26 @@ mod tests {

// Test various invalid addresses
let invalid_addrs = [
"not-an-ip:9999",
"256.256.256.256:9999",
"127.0.0.1:99999", // port too high
"127.0.0.1:-1", // negative port
"localhost", // hostname without port should be rejected
":9999", // missing IP
":::", // invalid IPv6
"localhost:abc", // non-numeric port
"", // empty string
"256.256.256.256:9999", // invalid IP octets
"127.0.0.1:99999", // port too high
"127.0.0.1:-1", // negative port
":9999", // missing hostname
"localhost:", // missing port
":", // missing hostname and port
":::", // invalid IPv6
"localhost:abc", // non-numeric port
];

for addr in &invalid_addrs {
let c_addr = CString::new(*addr).unwrap();
let result = dash_spv_ffi_config_add_peer(config, c_addr.as_ptr());
assert_eq!(result, FFIErrorCode::InvalidArgument as i32);
assert_eq!(
result,
FFIErrorCode::InvalidArgument as i32,
"Expected '{}' to be invalid",
addr
);

// Check error message
let error_ptr = dash_spv_ffi_get_last_error();
Expand All @@ -80,8 +86,10 @@ mod tests {
"192.168.1.1:8333",
"[::1]:9999",
"[2001:db8::1]:8333",
"127.0.0.1", // IP-only v4
"2001:db8::1", // IP-only v6
"127.0.0.1", // IP-only v4
"2001:db8::1", // IP-only v6
"localhost:9999", // Hostname with port
"localhost", // Hostname without port (uses default)
];

for addr in &valid_addrs {
Expand Down