use std::{
collections::HashMap,
fmt::{Display, Formatter, Result},
};
use zbus::zvariant::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddressType {
Public,
Random,
}
impl Display for AddressType {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Public => write!(f, "public"),
Self::Random => write!(f, "random"),
}
}
}
impl From<&str> for AddressType {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"random" => Self::Random,
_ => Self::Public,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PowerState {
On,
Off,
OffToOn,
OnToOff,
OffBlocked,
}
impl From<&str> for PowerState {
fn from(s: &str) -> Self {
match s {
"on" => Self::On,
"off" => Self::Off,
"off-enabling" => Self::OffToOn,
"on-disabling" => Self::OnToOff,
"off-blocked" => Self::OffBlocked,
_ => Self::Off,
}
}
}
impl Display for PowerState {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::On => write!(f, "on"),
Self::Off => write!(f, "off"),
Self::OffToOn => write!(f, "off-enabling"),
Self::OnToOff => write!(f, "on-disabling"),
Self::OffBlocked => write!(f, "off-blocked"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdapterRole {
Central,
Peripheral,
CentralPeripheral,
}
impl From<&str> for AdapterRole {
fn from(s: &str) -> Self {
match s {
"central" => Self::Central,
"peripheral" => Self::Peripheral,
"central-peripheral" => Self::CentralPeripheral,
_ => Self::Central,
}
}
}
impl Display for AdapterRole {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Central => write!(f, "central"),
Self::Peripheral => write!(f, "peripheral"),
Self::CentralPeripheral => write!(f, "central-peripheral"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DiscoveryTransport {
#[default]
Auto,
BrEdr,
Le,
}
impl From<&str> for DiscoveryTransport {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"bredr" => Self::BrEdr,
"le" => Self::Le,
_ => Self::Auto,
}
}
}
impl Display for DiscoveryTransport {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Auto => write!(f, "auto"),
Self::BrEdr => write!(f, "bredr"),
Self::Le => write!(f, "le"),
}
}
}
pub type DiscoveryFilter<'a> = HashMap<String, Value<'a>>;
#[derive(Debug, Clone, Default)]
pub struct DiscoveryFilterOptions<'a> {
pub uuids: Option<Vec<&'a str>>,
pub rssi: Option<i16>,
pub pathloss: Option<u16>,
pub transport: Option<DiscoveryTransport>,
pub duplicate_data: Option<bool>,
pub discoverable: Option<bool>,
pub pattern: Option<&'a str>,
pub auto_connect: Option<bool>,
}
impl<'a> DiscoveryFilterOptions<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn to_filter(self) -> DiscoveryFilter<'a> {
let mut filter = HashMap::new();
if let Some(uuids) = self.uuids {
let uuid_values: Vec<Value> = uuids.into_iter().map(Value::from).collect();
filter.insert("UUIDs".to_string(), Value::from(uuid_values));
}
if let Some(rssi) = self.rssi {
filter.insert("RSSI".to_string(), Value::from(rssi));
}
if let Some(pathloss) = self.pathloss {
filter.insert("Pathloss".to_string(), Value::from(pathloss));
}
if let Some(transport) = self.transport {
filter.insert("Transport".to_string(), Value::from(transport.to_string()));
}
if let Some(duplicate_data) = self.duplicate_data {
filter.insert("DuplicateData".to_string(), Value::from(duplicate_data));
}
if let Some(discoverable) = self.discoverable {
filter.insert("Discoverable".to_string(), Value::from(discoverable));
}
if let Some(pattern) = self.pattern {
filter.insert("Pattern".to_string(), Value::from(pattern));
}
if let Some(auto_connect) = self.auto_connect {
filter.insert("AutoConnect".to_string(), Value::from(auto_connect));
}
filter
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn address_type_from_str_parses_random() {
assert_eq!(AddressType::from("random"), AddressType::Random);
assert_eq!(AddressType::from("Random"), AddressType::Random);
assert_eq!(AddressType::from("RANDOM"), AddressType::Random);
}
#[test]
fn address_type_from_str_defaults_to_public() {
assert_eq!(AddressType::from("public"), AddressType::Public);
assert_eq!(AddressType::from("unknown"), AddressType::Public);
assert_eq!(AddressType::from(""), AddressType::Public);
}
#[test]
fn power_state_from_str_handles_all_variants() {
assert_eq!(PowerState::from("on"), PowerState::On);
assert_eq!(PowerState::from("off"), PowerState::Off);
assert_eq!(PowerState::from("off-enabling"), PowerState::OffToOn);
assert_eq!(PowerState::from("on-disabling"), PowerState::OnToOff);
}
#[test]
fn power_state_from_str_defaults_to_off() {
assert_eq!(PowerState::from("unknown"), PowerState::Off);
assert_eq!(PowerState::from(""), PowerState::Off);
}
#[test]
fn adapter_role_from_str_handles_all_variants() {
assert_eq!(AdapterRole::from("central"), AdapterRole::Central);
assert_eq!(AdapterRole::from("peripheral"), AdapterRole::Peripheral);
assert_eq!(
AdapterRole::from("central-peripheral"),
AdapterRole::CentralPeripheral
);
}
#[test]
fn adapter_role_from_str_defaults_to_central() {
assert_eq!(AdapterRole::from("unknown"), AdapterRole::Central);
assert_eq!(AdapterRole::from(""), AdapterRole::Central);
}
#[test]
fn discovery_transport_from_str_handles_all_variants() {
assert_eq!(DiscoveryTransport::from("auto"), DiscoveryTransport::Auto);
assert_eq!(DiscoveryTransport::from("Auto"), DiscoveryTransport::Auto);
assert_eq!(DiscoveryTransport::from("bredr"), DiscoveryTransport::BrEdr);
assert_eq!(DiscoveryTransport::from("BREDR"), DiscoveryTransport::BrEdr);
assert_eq!(DiscoveryTransport::from("le"), DiscoveryTransport::Le);
assert_eq!(DiscoveryTransport::from("LE"), DiscoveryTransport::Le);
}
#[test]
fn discovery_transport_from_str_defaults_to_auto() {
assert_eq!(
DiscoveryTransport::from("unknown"),
DiscoveryTransport::Auto
);
assert_eq!(DiscoveryTransport::from(""), DiscoveryTransport::Auto);
}
#[test]
fn discovery_filter_to_filter_with_empty_options_returns_empty_map() {
let options = DiscoveryFilterOptions::new();
let filter = options.to_filter();
assert!(filter.is_empty());
}
#[test]
fn discovery_filter_to_filter_with_uuids_includes_uuids_field() {
let options = DiscoveryFilterOptions {
uuids: Some(vec!["0000110a-0000-1000-8000-00805f9b34fb"]),
..Default::default()
};
let filter = options.to_filter();
assert!(filter.contains_key("UUIDs"));
assert_eq!(filter.len(), 1);
}
#[test]
fn discovery_filter_to_filter_with_rssi_includes_rssi_field() {
let options = DiscoveryFilterOptions {
rssi: Some(-70),
..Default::default()
};
let filter = options.to_filter();
assert!(filter.contains_key("RSSI"));
assert_eq!(filter.len(), 1);
}
#[test]
fn discovery_filter_to_filter_with_all_options_includes_all_fields() {
let options = DiscoveryFilterOptions {
uuids: Some(vec!["0000110a-0000-1000-8000-00805f9b34fb"]),
rssi: Some(-70),
pathloss: Some(10),
transport: Some(DiscoveryTransport::Le),
duplicate_data: Some(true),
discoverable: Some(false),
pattern: Some("dev"),
auto_connect: Some(true),
};
let filter = options.to_filter();
assert_eq!(filter.len(), 8);
assert!(filter.contains_key("UUIDs"));
assert!(filter.contains_key("RSSI"));
assert!(filter.contains_key("Pathloss"));
assert!(filter.contains_key("Transport"));
assert!(filter.contains_key("DuplicateData"));
assert!(filter.contains_key("Discoverable"));
assert!(filter.contains_key("Pattern"));
assert!(filter.contains_key("AutoConnect"));
}
}