Dihvice. Chat Sweep.
lowk traking devices ts way is TUFF. how do you even try to try.
This commit is contained in:
@@ -2,6 +2,9 @@
|
|||||||
param(
|
param(
|
||||||
[string]$PASSWD
|
[string]$PASSWD
|
||||||
)
|
)
|
||||||
|
. "$(Split-Path -Parent $PSScriptRoot)/scripts/lib.ps1"
|
||||||
|
|
||||||
|
$PASSWD = Get-DefaultPassword $PASSWD
|
||||||
|
|
||||||
# idk what this does it works like that then thats how it is
|
# idk what this does it works like that then thats how it is
|
||||||
pscp.exe -l root -scp -pw $PASSWD -r 192.168.100.1:/etc/ldlda_help $PSScriptRoot
|
pscp.exe -l root -scp -pw $PASSWD -r 192.168.100.1:/etc/ldlda_help $PSScriptRoot
|
||||||
|
|||||||
+190
-52
@@ -11,51 +11,65 @@ use axum::Router;
|
|||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use wakey_core::{
|
use wakey_core::{
|
||||||
DeviceFilters, DeviceQuery, DhcpLease, DhcpLeaseWithState, NeighborEntry, QueryInput, Status,
|
Device, DeviceFilters, DeviceInventory, DeviceQuery, DhcpLease, DhcpLeaseWithState, LeaseQuery,
|
||||||
WakeResult, WakeTarget,
|
NeighborEntry, Presence, Query, QueryInput, Status, WakeResult, WakeTarget,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type StatusResponse = Status<NeighborEntry>;
|
pub type StatusResponse = Status<NeighborEntry>;
|
||||||
|
|
||||||
pub async fn resolve_query(input: impl Into<String>) -> Result<DeviceQuery> {
|
pub async fn resolve_query(input: impl Into<String>) -> Result<DeviceQuery> {
|
||||||
|
query_to_device_query(resolve_selector(input).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn resolve_selector(input: impl Into<String>) -> Result<Query> {
|
||||||
Ok(
|
Ok(
|
||||||
match wakey_linux::devices::classify_query(input.into()).await {
|
match wakey_linux::devices::classify_query(input.into()).await {
|
||||||
QueryInput::Ip(ip_addr) => DeviceQuery {
|
QueryInput::Ip(ip_addr) => Query::Ip(ip_addr),
|
||||||
filter: DeviceFilters {
|
QueryInput::Mac(mac_addr) => Query::Mac(mac_addr),
|
||||||
ips: vec![ip_addr],
|
QueryInput::Dev(dev) => Query::Interface(dev),
|
||||||
..Default::default()
|
QueryInput::Nud(state) => Query::NeighborState(state),
|
||||||
},
|
QueryInput::Name(name) => Query::Text(name),
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
QueryInput::Mac(mac_addr) => DeviceQuery {
|
|
||||||
filter: DeviceFilters {
|
|
||||||
macs: vec![mac_addr],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
QueryInput::Dev(dev) => DeviceQuery {
|
|
||||||
filter: DeviceFilters {
|
|
||||||
devs: vec![dev],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
QueryInput::Nud(state) => DeviceQuery {
|
|
||||||
filter: DeviceFilters {
|
|
||||||
nuds: vec![state],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
QueryInput::Name(name) => DeviceQuery {
|
|
||||||
name: Some(name),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn query_to_device_query(query: Query) -> Result<DeviceQuery> {
|
||||||
|
Ok(match query {
|
||||||
|
Query::Ip(ip_addr) => DeviceQuery {
|
||||||
|
filter: DeviceFilters {
|
||||||
|
ips: vec![ip_addr],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Query::Mac(mac_addr) => DeviceQuery {
|
||||||
|
filter: DeviceFilters {
|
||||||
|
macs: vec![mac_addr],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Query::Interface(dev) => DeviceQuery {
|
||||||
|
filter: DeviceFilters {
|
||||||
|
devs: vec![dev],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Query::NeighborState(state) => DeviceQuery {
|
||||||
|
filter: DeviceFilters {
|
||||||
|
nuds: vec![state],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Query::Text(name) => DeviceQuery {
|
||||||
|
name: Some(name),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_status(query: DeviceQuery) -> Result<StatusResponse> {
|
pub async fn get_status(query: DeviceQuery) -> Result<StatusResponse> {
|
||||||
let table = wakey_linux::devices::query_status(&query).await?;
|
let table = wakey_linux::devices::query_status(&query).await?;
|
||||||
Ok(Status {
|
Ok(Status {
|
||||||
@@ -70,20 +84,14 @@ pub async fn get_status_for_input(input: impl Into<String>) -> Result<StatusResp
|
|||||||
get_status(query).await
|
get_status(query).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_leases(include_state: bool) -> Result<Vec<DhcpLeaseWithState>> {
|
pub async fn get_leases(query: LeaseQuery) -> Result<Vec<DhcpLeaseWithState>> {
|
||||||
let leases = wakey_linux::dhcp::read_dhcp_leases_with_names()
|
let leases = wakey_linux::dhcp::read_dhcp_leases_with_names()
|
||||||
.await
|
.await
|
||||||
.context("failed to read DHCP leases")?;
|
.context("failed to read DHCP leases")?;
|
||||||
if include_state {
|
if query.include_state {
|
||||||
Ok(wakey_linux::dhcp::enrich_leases_with_nud_state(leases).await)
|
Ok(wakey_linux::dhcp::enrich_leases_with_nud_state(leases).await)
|
||||||
} else {
|
} else {
|
||||||
Ok(leases
|
Ok(leases_without_state(leases))
|
||||||
.into_iter()
|
|
||||||
.map(|lease_line| DhcpLeaseWithState {
|
|
||||||
lease_line,
|
|
||||||
nud_state: None,
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,15 +103,7 @@ pub async fn wake_targets(targets: Vec<WakeTarget>) -> Result<WakeResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wake_from_query(input: impl Into<String>) -> Result<WakeResult> {
|
pub async fn wake_from_query(input: impl Into<String>) -> Result<WakeResult> {
|
||||||
let status = get_status_for_input(input).await?;
|
let targets = resolve_wake_targets(input).await?;
|
||||||
let targets = status
|
|
||||||
.table
|
|
||||||
.into_iter()
|
|
||||||
.map(|entry| WakeTarget {
|
|
||||||
ip: Some(entry.ip),
|
|
||||||
mac: entry.mac,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
wake_targets(targets).await
|
wake_targets(targets).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +117,110 @@ pub async fn get_ips(name: impl AsRef<str>) -> Result<Vec<std::net::IpAddr>> {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn resolve_devices(input: impl Into<String>) -> Result<Vec<Device>> {
|
||||||
|
let query = resolve_query(input).await?;
|
||||||
|
inventory(query).await.map(|inventory| inventory.devices)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn inventory(query: DeviceQuery) -> Result<DeviceInventory> {
|
||||||
|
let status = get_status(query.clone()).await?;
|
||||||
|
let leases = get_leases(LeaseQuery {
|
||||||
|
include_state: false,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
Ok(DeviceInventory {
|
||||||
|
devices: merge_devices(status.table, leases, &query),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn resolve_wake_targets(input: impl Into<String>) -> Result<Vec<WakeTarget>> {
|
||||||
|
let devices = resolve_devices(input).await?;
|
||||||
|
Ok(devices
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|device| {
|
||||||
|
let mac = device.macs.first().copied();
|
||||||
|
device
|
||||||
|
.ips
|
||||||
|
.into_iter()
|
||||||
|
.map(move |ip| WakeTarget { ip: Some(ip), mac })
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_devices(
|
||||||
|
neighbors: Vec<NeighborEntry>,
|
||||||
|
leases: Vec<DhcpLeaseWithState>,
|
||||||
|
query: &DeviceQuery,
|
||||||
|
) -> Vec<Device> {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
let mut by_mac: BTreeMap<String, (Vec<NeighborEntry>, Vec<DhcpLease>)> = BTreeMap::new();
|
||||||
|
|
||||||
|
for row in neighbors {
|
||||||
|
let key = row
|
||||||
|
.mac
|
||||||
|
.map(|m| m.to_string())
|
||||||
|
.unwrap_or_else(|| format!("ip:{}", row.ip));
|
||||||
|
by_mac.entry(key).or_default().0.push(row);
|
||||||
|
}
|
||||||
|
for lease in leases {
|
||||||
|
let key = lease.lease_line.mac.to_string();
|
||||||
|
by_mac.entry(key).or_default().1.push(lease.lease_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut devices: Vec<Device> = by_mac
|
||||||
|
.into_values()
|
||||||
|
.map(|(neighbors, leases)| Device::from_parts(neighbors, leases))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(name) = &query.name {
|
||||||
|
devices.retain(|device| device.names.iter().any(|n| n == name));
|
||||||
|
}
|
||||||
|
if !query.filter.devs.is_empty() {
|
||||||
|
devices.retain(|device| {
|
||||||
|
device
|
||||||
|
.interfaces
|
||||||
|
.iter()
|
||||||
|
.any(|iface| query.filter.devs.contains(iface))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if !query.filter.ips.is_empty() {
|
||||||
|
devices.retain(|device| device.ips.iter().any(|ip| query.filter.ips.contains(ip)));
|
||||||
|
}
|
||||||
|
if !query.filter.macs.is_empty() {
|
||||||
|
devices.retain(|device| {
|
||||||
|
device
|
||||||
|
.macs
|
||||||
|
.iter()
|
||||||
|
.any(|mac| query.filter.macs.contains(mac))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if !query.filter.nuds.is_empty() {
|
||||||
|
devices.retain(|device| {
|
||||||
|
device
|
||||||
|
.neighbors
|
||||||
|
.iter()
|
||||||
|
.any(|neighbor| query.filter.nuds.contains(&neighbor.state))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.sort_by(|a, b| {
|
||||||
|
presence_rank(b.presence)
|
||||||
|
.cmp(&presence_rank(a.presence))
|
||||||
|
.then_with(|| a.names.first().cmp(&b.names.first()))
|
||||||
|
});
|
||||||
|
devices
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn presence_rank(presence: Presence) -> u8 {
|
||||||
|
match presence {
|
||||||
|
Presence::Online => 3,
|
||||||
|
Presence::LikelyOnline => 2,
|
||||||
|
Presence::Unknown => 1,
|
||||||
|
Presence::Offline => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn http_app(static_root: std::path::PathBuf) -> Router {
|
pub fn http_app(static_root: std::path::PathBuf) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.nest("/api", route::api_router())
|
.nest("/api", route::api_router())
|
||||||
@@ -182,6 +286,17 @@ mod tests {
|
|||||||
assert_eq!(query.filter.nuds, vec![NeighborState::Reachable]);
|
assert_eq!(query.filter.nuds, vec![NeighborState::Reachable]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolve_selector_keeps_text_vs_structured() {
|
||||||
|
let selector = resolve_selector("reachable")
|
||||||
|
.await
|
||||||
|
.expect("resolve selector");
|
||||||
|
match selector {
|
||||||
|
Query::NeighborState(NeighborState::Reachable) => {}
|
||||||
|
_ => panic!("expected neighbor-state selector"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn leases_without_state_clears_nud_state() {
|
fn leases_without_state_clears_nud_state() {
|
||||||
let leases = vec![DhcpLease {
|
let leases = vec![DhcpLease {
|
||||||
@@ -195,6 +310,29 @@ mod tests {
|
|||||||
assert!(out[0].nud_state.is_none());
|
assert!(out[0].nud_state.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merge_devices_combines_lease_and_neighbor() {
|
||||||
|
let neighbors = vec![NeighborEntry {
|
||||||
|
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10)),
|
||||||
|
dev: Some("br-lan".into()),
|
||||||
|
mac: Some("aa:bb:cc:dd:ee:ff".parse().expect("mac")),
|
||||||
|
state: NeighborState::Reachable,
|
||||||
|
}];
|
||||||
|
let leases = vec![DhcpLeaseWithState {
|
||||||
|
lease_line: DhcpLease {
|
||||||
|
expires_epoch: 1,
|
||||||
|
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10)),
|
||||||
|
mac: "aa:bb:cc:dd:ee:ff".parse().expect("mac"),
|
||||||
|
name: Some("pc".into()),
|
||||||
|
},
|
||||||
|
nud_state: None,
|
||||||
|
}];
|
||||||
|
let devices = merge_devices(neighbors, leases, &DeviceQuery::default());
|
||||||
|
assert_eq!(devices.len(), 1);
|
||||||
|
assert_eq!(devices[0].presence, Presence::Online);
|
||||||
|
assert_eq!(devices[0].names, vec!["pc".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn wake_targets_marks_incomplete() {
|
async fn wake_targets_marks_incomplete() {
|
||||||
let out = wake_targets(vec![WakeTarget {
|
let out = wake_targets(vec![WakeTarget {
|
||||||
|
|||||||
+4
-1
@@ -110,7 +110,10 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
println!("{}", serde_json::to_string_pretty(&status)?);
|
println!("{}", serde_json::to_string_pretty(&status)?);
|
||||||
}
|
}
|
||||||
Command::Leases(args) => {
|
Command::Leases(args) => {
|
||||||
let leases = wakey::get_leases(args.include_state).await?;
|
let leases = wakey::get_leases(wakey_core::LeaseQuery {
|
||||||
|
include_state: args.include_state,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
println!("{}", serde_json::to_string_pretty(&leases)?);
|
println!("{}", serde_json::to_string_pretty(&leases)?);
|
||||||
}
|
}
|
||||||
Command::Wake(args) => {
|
Command::Wake(args) => {
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ pub async fn get_dhcp_leases(
|
|||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let include_state = include_state.as_deref().map(boolish_str).unwrap_or(false);
|
let include_state = include_state.as_deref().map(boolish_str).unwrap_or(false);
|
||||||
|
|
||||||
match crate::get_leases(include_state).await {
|
match crate::get_leases(wakey_core::LeaseQuery { include_state }).await {
|
||||||
Ok(leases) => (
|
Ok(leases) => (
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
Json(crate::compat::legacy_leases_from_domain(leases)),
|
Json(crate::compat::legacy_leases_from_domain(leases)),
|
||||||
|
|||||||
@@ -75,6 +75,117 @@ impl Ord for NeighborState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum Presence {
|
||||||
|
Online,
|
||||||
|
LikelyOnline,
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
Offline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NeighborState> for Presence {
|
||||||
|
fn from(value: NeighborState) -> Self {
|
||||||
|
match value {
|
||||||
|
NeighborState::Permanent | NeighborState::Reachable => Self::Online,
|
||||||
|
NeighborState::Stale => Self::LikelyOnline,
|
||||||
|
NeighborState::Failed => Self::Offline,
|
||||||
|
NeighborState::Delay
|
||||||
|
| NeighborState::Probe
|
||||||
|
| NeighborState::Incomplete
|
||||||
|
| NeighborState::Noarp
|
||||||
|
| NeighborState::None => Self::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
|
||||||
|
pub struct DeviceId {
|
||||||
|
#[serde(with = "mac")]
|
||||||
|
pub mac: MacAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct Device {
|
||||||
|
pub id: Option<DeviceId>,
|
||||||
|
pub names: Vec<String>,
|
||||||
|
pub ips: Vec<IpAddr>,
|
||||||
|
#[serde(with = "mac::vec_mac")]
|
||||||
|
pub macs: Vec<MacAddr>,
|
||||||
|
pub interfaces: Vec<String>,
|
||||||
|
pub neighbors: Vec<NeighborEntry>,
|
||||||
|
pub leases: Vec<DhcpLease>,
|
||||||
|
pub presence: Presence,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn from_parts(neighbors: Vec<NeighborEntry>, leases: Vec<DhcpLease>) -> Self {
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
let mut names = BTreeSet::new();
|
||||||
|
let mut ips = BTreeSet::new();
|
||||||
|
let mut macs = BTreeSet::new();
|
||||||
|
let mut interfaces = BTreeSet::new();
|
||||||
|
let mut presence = Presence::Unknown;
|
||||||
|
|
||||||
|
for lease in &leases {
|
||||||
|
ips.insert(lease.ip);
|
||||||
|
macs.insert(lease.mac);
|
||||||
|
if let Some(name) = &lease.name {
|
||||||
|
names.insert(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for neighbor in &neighbors {
|
||||||
|
ips.insert(neighbor.ip);
|
||||||
|
if let Some(mac) = neighbor.mac {
|
||||||
|
macs.insert(mac);
|
||||||
|
}
|
||||||
|
if let Some(dev) = &neighbor.dev {
|
||||||
|
interfaces.insert(dev.clone());
|
||||||
|
}
|
||||||
|
presence = std::cmp::max(
|
||||||
|
presence_rank(presence),
|
||||||
|
presence_rank(neighbor.state.into()),
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let macs: Vec<MacAddr> = macs.into_iter().collect();
|
||||||
|
Self {
|
||||||
|
id: macs.first().copied().map(|mac| DeviceId { mac }),
|
||||||
|
names: names.into_iter().collect(),
|
||||||
|
ips: ips.into_iter().collect(),
|
||||||
|
macs,
|
||||||
|
interfaces: interfaces.into_iter().collect(),
|
||||||
|
neighbors,
|
||||||
|
leases,
|
||||||
|
presence,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn presence_rank(presence: Presence) -> u8 {
|
||||||
|
match presence {
|
||||||
|
Presence::Online => 3,
|
||||||
|
Presence::LikelyOnline => 2,
|
||||||
|
Presence::Unknown => 1,
|
||||||
|
Presence::Offline => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Presence {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
3 => Self::Online,
|
||||||
|
2 => Self::LikelyOnline,
|
||||||
|
0 => Self::Offline,
|
||||||
|
_ => Self::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
|
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
|
||||||
pub struct DeviceQuery {
|
pub struct DeviceQuery {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
@@ -129,6 +240,16 @@ pub struct DhcpLeaseWithState {
|
|||||||
pub nud_state: Option<NeighborState>,
|
pub nud_state: Option<NeighborState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct LeaseQuery {
|
||||||
|
pub include_state: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Serialize)]
|
||||||
|
pub struct DeviceInventory {
|
||||||
|
pub devices: Vec<Device>,
|
||||||
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub struct WakeTarget {
|
pub struct WakeTarget {
|
||||||
@@ -190,6 +311,15 @@ pub enum QueryInput {
|
|||||||
Name(String),
|
Name(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Query {
|
||||||
|
Text(String),
|
||||||
|
Ip(IpAddr),
|
||||||
|
Mac(MacAddr),
|
||||||
|
Interface(String),
|
||||||
|
NeighborState(NeighborState),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, thiserror::Error)]
|
#[derive(Debug, Display, thiserror::Error)]
|
||||||
pub enum NeighborParseError {
|
pub enum NeighborParseError {
|
||||||
IpWhere,
|
IpWhere,
|
||||||
|
|||||||
@@ -102,4 +102,30 @@ pub mod mac {
|
|||||||
.map_err(serde::de::Error::custom)
|
.map_err(serde::de::Error::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vec<DisplayFromStr> ?
|
||||||
|
pub mod vec_mac {
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let strings: Vec<String> = macs.iter().map(ToString::to_string).collect();
|
||||||
|
strings.serialize(serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<MacAddr>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let strings = Vec::<String>::deserialize(deserializer)?;
|
||||||
|
strings
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.parse())
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user