the fmt of cool

This commit is contained in:
lda
2025-10-21 01:07:41 +07:00 Unverified
parent d7c34ee9f5
commit a534c58262
6 changed files with 219 additions and 47 deletions
+1 -1
View File
@@ -16,4 +16,4 @@
pub mod neighbor;
pub mod utils;
// i want a generalized way to build and call
// i want a generalized way to build and call
+3 -5
View File
@@ -1,11 +1,11 @@
//! ```
//! ip -j n s
//! ```
//!
//!
//! yes. this is a real call.
use crate::utils::serialize::mac::{des_opm, ser_opm};
use std::{borrow::Cow, net::IpAddr};
use std::net::IpAddr;
use macaddr::MacAddr;
use serde::{Deserialize, Serialize};
@@ -21,7 +21,7 @@ pub struct NeighborInput {
nud: Vec<NUDState>,
}
// as input this must be lowercase. as output it is uppercase
// as input this must be lowercase. as output it is uppercase
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, Serialize, Deserialize)]
#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
#[serde(rename_all = "UPPERCASE")]
@@ -68,5 +68,3 @@ pub struct NeighborItem {
lladdr: Option<MacAddr>,
state: Vec<NUDState>,
}
+1 -1
View File
@@ -1 +1 @@
pub mod serialize;
pub mod serialize;
+40 -39
View File
@@ -1,44 +1,45 @@
use macaddr::MacAddr;
use serde::{self, Deserialize, Deserializer, de::Error as DeError};
use serde::{Serialize, Serializer, de};
pub fn serialize_macs<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let strings: Vec<String> = macs.iter().map(|m| m.to_string()).collect();
serde::Serialize::serialize(&strings, serializer)
}
use macaddr::MacAddr;
use serde::{self, Deserialize, Deserializer, de::Error as DeError};
use serde::{Serialize, Serializer, de};
/// Serialize a MacAddr as a string
pub fn serialize_mac<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&mac.to_string())
}
pub fn serialize_macs<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let strings: Vec<String> = macs.iter().map(|m| m.to_string()).collect();
serde::Serialize::serialize(&strings, serializer)
}
/// Deserialize a MacAddr from a string
pub fn deserialize_mac<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
where
D: Deserializer<'de>,
{
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
s.parse::<MacAddr>().map_err(DeError::custom)
}
/// Serialize a MacAddr as a string
pub fn serialize_mac<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&mac.to_string())
}
/// serialize an [`Option<MacAddr>`]
pub fn ser_opm<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
}
/// Deserialize a MacAddr from a string
pub fn deserialize_mac<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
where
D: Deserializer<'de>,
{
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
s.parse::<MacAddr>().map_err(DeError::custom)
}
/// deserialize an [`Option<MacAddr>`]
pub fn des_opm<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
where
D: serde::Deserializer<'de>,
{
Option::<&str>::deserialize(des)?
.map(str::parse)
.transpose()
.map_err(de::Error::custom)
}
/// serialize an [`Option<MacAddr>`]
pub fn ser_opm<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
}
/// deserialize an [`Option<MacAddr>`]
pub fn des_opm<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
where
D: serde::Deserializer<'de>,
{
Option::<&str>::deserialize(des)?
.map(str::parse)
.transpose()
.map_err(de::Error::custom)
}
+1 -1
View File
@@ -1,2 +1,2 @@
pub mod mac;
pub mod vec;
pub mod vec;
+173
View File
@@ -0,0 +1,173 @@
use macaddr::MacAddr6;
use serde::Deserialize;
use std::{net::IpAddr, process::Command};
// Raw JSON shape from ip -j -4 address show
#[derive(Debug, Deserialize)]
struct IpJAddrInfo {
family: Option<String>,
local: Option<String>,
broadcast: Option<String>,
scope: Option<String>,
label: Option<String>,
prefixlen: Option<u8>,
// many more exist; we only take what we need
}
#[derive(Debug, Deserialize)]
struct IpJAddrEntry {
ifname: String,
// interface MAC address (present on non-loopback):
address: Option<String>,
addr_info: Option<Vec<IpJAddrInfo>>,
}
#[derive(Debug, Clone)]
pub struct AddrInfo {
pub local: IpAddr,
pub broadcast: Option<IpAddr>,
pub scope: Option<String>,
pub label: Option<String>,
pub prefixlen: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct IpAEntry {
pub ifname: String,
pub mac: Option<MacAddr6>,
pub addr_info: Vec<AddrInfo>,
// IPv4 only (inet)
}
// Run ip -j -4 address show and deserialize
fn read_ip_json() -> Result<Vec<IpJAddrEntry>, Box<dyn std::error::Error>> {
let out = Command::new("ip")
.args(["-j", "-4", "address", "show"])
.output()?;
if !out.status.success() {
return Err(format!("ip exited with {}", out.status).into());
}
let entries: Vec<IpJAddrEntry> = serde_json::from_slice(&out.stdout)?;
Ok(entries)
}
fn parse_ip(s: &str) -> Option<IpAddr> {
s.parse::<IpAddr>().ok()
}
fn parse_mac(s: &str) -> Option<MacAddr6> {
// ip outputs lowercase aa:bb:..., which MacAddr6 can parse
s.parse::<MacAddr6>().ok()
}
// Public: read and convert to a cleaned model
pub fn get_ip_addr_entries() -> Result<Vec<IpAEntry>, Box<dyn std::error::Error>> {
let raw = read_ip_json()?;
let mut out = Vec::new();
for e in raw {
let mac = e.address.as_deref().and_then(parse_mac);
let mut infos = Vec::new();
if let Some(list) = e.addr_info {
for ai in list {
// keep only IPv4
if ai.family.as_deref() != Some("inet") {
continue;
}
if let Some(local) = ai.local.as_deref().and_then(parse_ip) {
let broadcast = ai.broadcast.as_deref().and_then(parse_ip);
infos.push(AddrInfo {
local,
broadcast,
scope: ai.scope,
label: ai.label,
prefixlen: ai.prefixlen,
});
}
}
}
out.push(IpAEntry {
ifname: e.ifname,
mac,
addr_info: infos,
});
}
Ok(out)
}
// Helper: first “good” (global) broadcast on an interface
pub fn broadcast_for_ifname(ifname: &str) -> Result<Option<IpAddr>, Box<dyn std::error::Error>> {
let entries = get_ip_addr_entries()?;
let dev = entries.into_iter().find(|e| e.ifname == ifname);
if let Some(dev) = dev {
// Prefer scope=global with a broadcast; else any broadcast
if let Some(b) = dev
.addr_info
.iter()
.filter(|ai| ai.scope.as_deref() == Some("global"))
.filter_map(|ai| ai.broadcast)
.next()
{
return Ok(Some(b));
}
if let Some(b) = dev.addr_info.iter().filter_map(|ai| ai.broadcast).next() {
return Ok(Some(b));
}
}
Ok(None)
}
// Helper: broadcast for a given interface MAC
pub fn broadcast_for_mac(
mac: MacAddr6,
) -> Result<Option<(String, IpAddr)>, Box<dyn std::error::Error>> {
let entries = get_ip_addr_entries()?;
for e in entries {
if let Some(m) = e.mac {
if m == mac {
// prefer global broadcast
if let Some(b) = e
.addr_info
.iter()
.filter(|ai| ai.scope.as_deref() == Some("global"))
.filter_map(|ai| ai.broadcast)
.next()
{
return Ok(Some((e.ifname, b)));
}
if let Some(b) = e.addr_info.iter().filter_map(|ai| ai.broadcast).next() {
return Ok(Some((e.ifname, b)));
}
}
}
}
Ok(None)
}
// Fallback: if nothing else, use limited broadcast (may be filtered on some networks)
pub fn fallback_broadcast() -> IpAddr {
// 255.255.255.255
IpAddr::from([255, 255, 255, 255])
}
// Convenience: all broadcasts grouped by interface (IPv4)
pub fn all_broadcasts() -> Result<Vec<(String, IpAddr)>, Box<dyn std::error::Error>> {
let Some(r) = Some(12) else {
e.addr_info
.iter()
.filter(|ai| ai.scope.as_deref() == Some("global"))
.filter_map(|ai| ai.broadcast)
};
let entries = get_ip_addr_entries()?;
let mut out = Vec::new();
for e in entries {
for ai in e.addr_info {
if let Some(b) = ai.broadcast {
out.push((e.ifname.clone(), b));
}
}
}
Ok(out)
}