the Chat sweep
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "wakey-core"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
macaddr = { version = "1", features = ["serde", "serde_std"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_with = { version = "3", features = ["json"] }
|
||||
strum = { version = "0", features = ["derive", "strum_macros"] }
|
||||
thiserror = "2"
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod model;
|
||||
pub mod parse;
|
||||
|
||||
pub use model::*;
|
||||
@@ -0,0 +1,242 @@
|
||||
use macaddr::MacAddr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use serde_with::{DisplayFromStr, OneOrMany, serde_as};
|
||||
use std::{net::IpAddr, str::FromStr};
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::parse::mac;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
|
||||
pub struct NeighborEntry {
|
||||
pub ip: IpAddr,
|
||||
pub dev: Option<String>,
|
||||
#[serde(with = "mac::option_mac")]
|
||||
pub mac: Option<MacAddr>,
|
||||
pub state: NeighborState,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, Serialize, Deserialize, Default,
|
||||
)]
|
||||
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum NeighborState {
|
||||
Permanent,
|
||||
Noarp,
|
||||
Reachable,
|
||||
Stale,
|
||||
Incomplete,
|
||||
Delay,
|
||||
Probe,
|
||||
Failed,
|
||||
#[serde(other)]
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
impl NeighborState {
|
||||
pub const fn as_ip_neigh_arg(self) -> &'static str {
|
||||
match self {
|
||||
NeighborState::Permanent => "permanent",
|
||||
NeighborState::Reachable => "reachable",
|
||||
NeighborState::Stale => "stale",
|
||||
NeighborState::Delay => "delay",
|
||||
NeighborState::Probe => "probe",
|
||||
NeighborState::Incomplete => "incomplete",
|
||||
NeighborState::Noarp => "noarp",
|
||||
NeighborState::None => "none",
|
||||
NeighborState::Failed => "failed",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn rank(self) -> u8 {
|
||||
match self {
|
||||
NeighborState::Permanent | NeighborState::Reachable => 5,
|
||||
NeighborState::Stale => 4,
|
||||
NeighborState::Delay | NeighborState::Probe | NeighborState::Incomplete => 3,
|
||||
NeighborState::Noarp => 2,
|
||||
NeighborState::None => 1,
|
||||
NeighborState::Failed => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for NeighborState {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for NeighborState {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.rank().cmp(&other.rank())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
|
||||
pub struct DeviceQuery {
|
||||
pub name: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub filter: DeviceFilters,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize)]
|
||||
pub struct DeviceFilters {
|
||||
#[serde_as(as = "OneOrMany<_>")]
|
||||
#[serde(default)]
|
||||
pub ips: Vec<IpAddr>,
|
||||
#[serde_as(as = "OneOrMany<_>")]
|
||||
#[serde(default)]
|
||||
pub devs: Vec<String>,
|
||||
#[serde_as(as = "OneOrMany<_>")]
|
||||
#[serde(default)]
|
||||
pub nuds: Vec<NeighborState>,
|
||||
#[serde_as(as = "OneOrMany<DisplayFromStr>")]
|
||||
#[serde(default)]
|
||||
pub macs: Vec<MacAddr>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Status<T> {
|
||||
pub name: Option<String>,
|
||||
pub table: Vec<T>,
|
||||
pub filters: DeviceFilters,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Hash, Deserialize)]
|
||||
pub struct NamePath {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct DhcpLease {
|
||||
pub expires_epoch: u64,
|
||||
pub ip: IpAddr,
|
||||
#[serde(with = "mac")]
|
||||
pub mac: MacAddr,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct DhcpLeaseWithState {
|
||||
#[serde(flatten)]
|
||||
pub lease_line: DhcpLease,
|
||||
pub nud_state: Option<NeighborState>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct WakeTarget {
|
||||
#[serde(default)]
|
||||
pub ip: Option<IpAddr>,
|
||||
#[serde(default, with = "mac::option_mac")]
|
||||
pub mac: Option<MacAddr>,
|
||||
}
|
||||
|
||||
impl WakeTarget {
|
||||
pub const fn is_complete(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self {
|
||||
ip: Some(_),
|
||||
mac: Some(_)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct WakeResult {
|
||||
pub result: Vec<WakeTargetResult>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Clone, Copy)]
|
||||
pub struct WakeTargetResult {
|
||||
#[serde(flatten)]
|
||||
pub target: WakeTarget,
|
||||
pub status: WakeStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WakeStatus {
|
||||
Succeed,
|
||||
NonexistentAddress,
|
||||
WrongSize,
|
||||
Incomplete,
|
||||
}
|
||||
|
||||
impl WakeTargetResult {
|
||||
pub const fn incomplete(target: WakeTarget) -> Self {
|
||||
Self {
|
||||
target,
|
||||
status: WakeStatus::Incomplete,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryInput {
|
||||
Ip(IpAddr),
|
||||
Mac(MacAddr),
|
||||
Dev(String),
|
||||
Nud(NeighborState),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, thiserror::Error)]
|
||||
pub enum NeighborParseError {
|
||||
IpWhere,
|
||||
IpParseError(#[from] std::net::AddrParseError),
|
||||
MacParseError(#[from] macaddr::ParseError),
|
||||
StateWhere,
|
||||
StateParseError(#[from] strum::ParseError),
|
||||
}
|
||||
|
||||
pub fn parse_neighbor_line(s: &str) -> Result<NeighborEntry, NeighborParseError> {
|
||||
let mut it = s.split_whitespace();
|
||||
let ip: IpAddr = it.next().ok_or(NeighborParseError::IpWhere)?.parse()?;
|
||||
|
||||
let mut dev: Option<String> = None;
|
||||
let mut mac: Option<MacAddr> = None;
|
||||
let mut state: Option<NeighborState> = None;
|
||||
let mut last_tok: Option<&str> = None;
|
||||
|
||||
while let Some(tok) = it.next() {
|
||||
match tok {
|
||||
"dev" => dev = it.next().map(str::to_string),
|
||||
"lladdr" => {
|
||||
mac = it.next().map(|m| m.parse()).transpose()?;
|
||||
}
|
||||
"nud" => {
|
||||
state = it.next().map(|st| st.parse()).transpose()?;
|
||||
}
|
||||
other => last_tok = Some(other),
|
||||
}
|
||||
}
|
||||
|
||||
if state.is_none() && let Some(st) = last_tok {
|
||||
state = Some(st.parse()?);
|
||||
}
|
||||
|
||||
Ok(NeighborEntry {
|
||||
ip,
|
||||
dev,
|
||||
mac,
|
||||
state: state.ok_or(NeighborParseError::StateWhere)?,
|
||||
})
|
||||
}
|
||||
|
||||
impl FromStr for NeighborEntry {
|
||||
type Err = NeighborParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
parse_neighbor_line(s)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
pub fn parse_numeric_ipv4(s: &str) -> Option<std::net::IpAddr> {
|
||||
let s = s.trim();
|
||||
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X"))
|
||||
&& hex.chars().all(|c| c.is_ascii_hexdigit())
|
||||
&& let Ok(n) = u32::from_str_radix(hex, 16)
|
||||
{
|
||||
return Some(std::net::IpAddr::V4(std::net::Ipv4Addr::from(n)));
|
||||
}
|
||||
if s.chars().all(|c| c.is_ascii_digit()) && let Ok(n) = s.parse::<u32>() {
|
||||
return Some(std::net::IpAddr::V4(std::net::Ipv4Addr::from(n)));
|
||||
}
|
||||
if s.len() > 1
|
||||
&& s.as_bytes()[0] == b'0'
|
||||
&& s.chars().all(|c| matches!(c, '0'..='7'))
|
||||
&& let Ok(n) = u32::from_str_radix(s, 8)
|
||||
{
|
||||
return Some(std::net::IpAddr::V4(std::net::Ipv4Addr::from(n)));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn extract_host(input: &str) -> &str {
|
||||
let mut s = input.trim();
|
||||
if let Some(idx) = s.find("://") {
|
||||
s = &s[idx + 3..];
|
||||
} else if let Some(rest) = s.strip_prefix("//") {
|
||||
s = rest;
|
||||
}
|
||||
if let Some((_, host)) = s.rsplit_once('@') {
|
||||
s = host;
|
||||
}
|
||||
if let Some(host) = s.strip_prefix('[') {
|
||||
if let Some(end) = host.find(']') {
|
||||
s = &host[..end];
|
||||
}
|
||||
} else {
|
||||
if let Some(pos) = s.find('/') {
|
||||
s = &s[..pos];
|
||||
}
|
||||
if let Some((host, port)) = s.rsplit_once(':')
|
||||
&& s.matches(':').count() == 1
|
||||
&& port.chars().all(|c| c.is_ascii_digit())
|
||||
{
|
||||
s = host;
|
||||
}
|
||||
}
|
||||
s.trim()
|
||||
}
|
||||
|
||||
pub fn boolish_str(s: &str) -> bool {
|
||||
let t = s.trim().to_ascii_lowercase();
|
||||
if t.is_empty() {
|
||||
return true;
|
||||
}
|
||||
matches!(t.as_str(), "1" | "true" | "yes" | "on" | "y")
|
||||
|| (!matches!(t.as_str(), "0" | "false" | "no" | "off" | "n")
|
||||
&& t.parse::<u64>().map(|n| n != 0).unwrap_or(false))
|
||||
}
|
||||
|
||||
pub mod mac {
|
||||
use macaddr::MacAddr;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S>(m: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&m.to_string())
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
s.parse().map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
pub mod option_mac {
|
||||
use macaddr::MacAddr;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S>(m: &Option<MacAddr>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match m {
|
||||
Some(mac) => serializer.serialize_some(&mac.to_string()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<MacAddr>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = Option::<String>::deserialize(deserializer)?;
|
||||
s.map(|x| x.parse()).transpose().map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user