use crate::ffi::c_str;
use avahi_sys::{
AvahiAddress, AvahiClient, avahi_address_snprint, avahi_alternative_service_name,
avahi_strerror,
};
use libc::c_char;
use std::ffi::CStr;
use crate::{NetworkInterface, Result, ServiceType};
pub unsafe fn avahi_address_to_string(addr: *const AvahiAddress) -> String {
assert_not_null!(addr);
let addr_str = c_string!(alloc(avahi_sys::AVAHI_ADDRESS_STR_MAX as usize));
unsafe {
avahi_address_snprint(
addr_str.as_ptr() as *mut c_char,
avahi_sys::AVAHI_ADDRESS_STR_MAX as usize,
addr,
);
}
String::from(c_str::to_str(&addr_str))
.trim_matches(char::from(0))
.to_string()
}
pub unsafe fn get_error<'a>(code: i32) -> &'a str {
unsafe {
CStr::from_ptr(avahi_strerror(code))
.to_str()
.expect("could not fetch Avahi error string")
}
}
pub unsafe fn get_last_error<'a>(client: *mut AvahiClient) -> &'a str {
unsafe { get_error(avahi_sys::avahi_client_errno(client)) }
}
pub fn interface_index(interface: NetworkInterface) -> i32 {
match interface {
NetworkInterface::Unspec => avahi_sys::AVAHI_IF_UNSPEC,
NetworkInterface::AtIndex(i) => i as i32,
}
}
pub fn interface_from_index(index: i32) -> NetworkInterface {
match index {
avahi_sys::AVAHI_IF_UNSPEC => NetworkInterface::Unspec,
_ => NetworkInterface::AtIndex(index as u32),
}
}
pub unsafe fn sys_exec<F: FnOnce() -> i32>(func: F, message: &str) -> Result<()> {
let err = func();
if err < 0 {
Err(format!("{}: `{}`", message, unsafe { get_error(err) }).into())
} else {
Ok(())
}
}
pub fn format_service_type(service_type: &ServiceType) -> String {
format!("_{}._{}", service_type.name(), service_type.protocol())
}
pub fn format_browser_type(service_type: &ServiceType) -> String {
let kind = format_service_type(service_type);
let sub_types = service_type.sub_types();
if sub_types.is_empty() {
return kind;
}
if sub_types.len() > 1 {
warn!(
"browsing by multiple sub-types is not supported on Avahi devices, using first sub-type only"
);
}
format_sub_type(&sub_types[0], &kind)
}
pub fn format_sub_type(sub_type: &str, kind: &str) -> String {
format!(
"{}{}._sub.{}",
if sub_type.starts_with('_') { "" } else { "_" },
sub_type,
kind
)
}
pub unsafe fn alternative_service_name(name: &CStr) -> &CStr {
unsafe { CStr::from_ptr(avahi_alternative_service_name(name.as_ptr())) }
}
#[cfg(test)]
mod tests {
use super::*;
use avahi_sys::{
AVAHI_PROTO_INET, AVAHI_PROTO_INET6, AvahiAddress__bindgen_ty_1, AvahiIPv4Address,
AvahiIPv6Address,
};
#[test]
fn sys_exec_returns_ok_for_success() {
assert!(unsafe { sys_exec(|| 0, "test") }.is_ok());
}
#[test]
fn sys_exec_returns_error_for_failure() {
assert_eq!(
unsafe { sys_exec(|| avahi_sys::AVAHI_ERR_FAILURE, "uh oh spaghetti-o") },
Err("uh oh spaghetti-o: `Operation failed`".into())
);
}
#[test]
fn interface_index_returns_unspec_for_unspec() {
assert_eq!(
interface_index(NetworkInterface::Unspec),
avahi_sys::AVAHI_IF_UNSPEC
);
}
#[test]
fn interface_index_returns_index_for_index() {
assert_eq!(interface_index(NetworkInterface::AtIndex(1)), 1);
}
#[test]
fn interface_from_index_returns_unspec_for_avahi_unspec() {
assert_eq!(
interface_from_index(avahi_sys::AVAHI_IF_UNSPEC),
NetworkInterface::Unspec
);
}
#[test]
fn interface_from_index_returns_index_for_avahi_index() {
assert_eq!(interface_from_index(1), NetworkInterface::AtIndex(1));
}
#[test]
fn format_service_type_returns_valid_string() {
assert_eq!(
format_service_type(&ServiceType::new("http", "tcp").unwrap()),
"_http._tcp"
);
}
#[test]
fn format_browser_type_returns_valid_string() {
assert_eq!(
format_browser_type(&ServiceType::new("http", "tcp").unwrap()),
"_http._tcp"
);
}
#[test]
fn format_browser_type_returns_string_with_sub_types() {
assert_eq!(
format_browser_type(
&ServiceType::with_sub_types("http", "tcp", vec!["printer1", "printer2"]).unwrap()
),
"_printer1._sub._http._tcp"
);
}
#[test]
fn format_sub_type_returns_valid_string() {
assert_eq!(format_sub_type("foo", "_http._tcp"), "_foo._sub._http._tcp");
}
#[test]
fn format_sub_type_strips_leading_underscore() {
assert_eq!(
format_sub_type("_foo", "_http._tcp"),
"_foo._sub._http._tcp"
);
}
#[test]
fn get_error_returns_valid_error_string() {
assert_eq!(
unsafe { get_error(avahi_sys::AVAHI_ERR_FAILURE) },
"Operation failed"
);
}
#[test]
fn address_to_string_returns_correct_ipv4_string() {
let ipv4_addr = AvahiAddress {
proto: AVAHI_PROTO_INET,
data: AvahiAddress__bindgen_ty_1 {
ipv4: AvahiIPv4Address {
address: 0x6464a8c0, },
},
};
unsafe {
assert_eq!(avahi_address_to_string(&ipv4_addr), "192.168.100.100");
}
}
#[test]
fn address_to_string_returns_correct_ipv6_string() {
let ipv6_addr = AvahiAddress {
proto: AVAHI_PROTO_INET6,
data: AvahiAddress__bindgen_ty_1 {
ipv6: AvahiIPv6Address {
address: [
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc, 0xde, 0xf0,
],
},
},
};
unsafe {
assert_eq!(
avahi_address_to_string(&ipv6_addr),
"fe80::1234:5678:9abc:def0"
);
}
}
}