Working packet parser
This commit is contained in:
parent
f510d57ab4
commit
309e463578
15
Cargo.toml
15
Cargo.toml
@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "<crate_name>"
|
||||
name = "netbrowse"
|
||||
version = "0.1.0"
|
||||
authors = ["Evan Pratten <ewpratten@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "<description>"
|
||||
documentation = "https://docs.rs/<crate_name>"
|
||||
description = "A graphical frontend to avahi-browse"
|
||||
documentation = "https://docs.rs/netbrowse"
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/ewpratten/<repo_name>"
|
||||
repository = "https://github.com/ewpratten/<repo_name>"
|
||||
homepage = "https://github.com/ewpratten/netbrowse"
|
||||
repository = "https://github.com/ewpratten/netbrowse"
|
||||
license = "GPL-3.0"
|
||||
keywords = []
|
||||
categories = []
|
||||
@ -15,3 +15,8 @@ categories = []
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
which = "4.2.2"
|
||||
colored = "2.0.0"
|
||||
eframe = "0.15.0"
|
||||
ipnet = "2.3.1"
|
||||
thiserror = "1.0.30"
|
14
README.md
14
README.md
@ -1,9 +1,9 @@
|
||||
# <repo_name>
|
||||
[](https://crates.io/crates/<crate_name>)
|
||||
[](https://docs.rs/<crate_name>)
|
||||
[](https://github.com/Ewpratten/<repo_name>/actions/workflows/build.yml)
|
||||
[](https://github.com/Ewpratten/<repo_name>/actions/workflows/clippy.yml)
|
||||
[](https://github.com/Ewpratten/<repo_name>/actions/workflows/audit.yml)
|
||||
# netbrowse
|
||||
[](https://crates.io/crates/netbrowse)
|
||||
[](https://docs.rs/netbrowse)
|
||||
[](https://github.com/Ewpratten/netbrowse/actions/workflows/build.yml)
|
||||
[](https://github.com/Ewpratten/netbrowse/actions/workflows/clippy.yml)
|
||||
[](https://github.com/Ewpratten/netbrowse/actions/workflows/audit.yml)
|
||||
|
||||
|
||||
repo description
|
||||
@ -13,5 +13,5 @@ repo description
|
||||
This crate can be installed via `cargo` with:
|
||||
|
||||
```sh
|
||||
cargo install <crate_name>
|
||||
cargo install netbrowse
|
||||
```
|
||||
|
183
src/ipc.rs
Normal file
183
src/ipc.rs
Normal file
@ -0,0 +1,183 @@
|
||||
//! This module handles pulling and parsing data from an `avahi-browse` subprocess in a way that the GUI can render
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
io::BufRead,
|
||||
process::{Child, Stdio},
|
||||
sync::mpsc::{self, Receiver, Sender},
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Defines possible errors generated by the packet parsing process
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PacketParseError {
|
||||
#[error("Failed to parse mode char: {0}")]
|
||||
ModeParseError(char),
|
||||
#[error("Failed to internet protocol type: {0}")]
|
||||
IpTypeParseError(String),
|
||||
#[error("Ran out of arguments")]
|
||||
NotEnoughArgsError,
|
||||
}
|
||||
|
||||
/// Tries to unwrap a string from an arg iter, or just returns an error
|
||||
fn try_unwrap_arg(arg: Option<&str>) -> Result<&str, PacketParseError> {
|
||||
arg.ok_or(PacketParseError::NotEnoughArgsError)
|
||||
}
|
||||
|
||||
/// Defines the type of packet received from the `avahi-browse` subprocess
|
||||
#[derive(Debug)]
|
||||
pub enum PacketMode {
|
||||
/// A new service was discovered
|
||||
New,
|
||||
/// An existing service was updated
|
||||
Update,
|
||||
/// An existing service was removed
|
||||
Remove,
|
||||
}
|
||||
|
||||
impl TryFrom<char> for PacketMode {
|
||||
type Error = PacketParseError;
|
||||
|
||||
/// Parses one of avahi's update chars into something we can make use of
|
||||
fn try_from(c: char) -> Result<Self, Self::Error> {
|
||||
match c {
|
||||
'+' => Ok(PacketMode::New),
|
||||
'=' => Ok(PacketMode::Update),
|
||||
'-' => Ok(PacketMode::Remove),
|
||||
_ => Err(PacketParseError::ModeParseError(c)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to specify weather a packet came over IPv4 or IPv6
|
||||
#[derive(Debug)]
|
||||
pub enum IpType {
|
||||
V4,
|
||||
V6,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for IpType {
|
||||
type Error = PacketParseError;
|
||||
|
||||
/// Parses the type of IP address from the avahi-browse output
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
match s.as_str() {
|
||||
"IPv4" => Ok(IpType::V4),
|
||||
"IPv6" => Ok(IpType::V6),
|
||||
_ => Err(PacketParseError::IpTypeParseError(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An information packet parsed from a `avahi-browse` subprocess
|
||||
#[derive(Debug)]
|
||||
pub struct MdnsPacket {
|
||||
/// The type of packet received
|
||||
pub mode: PacketMode,
|
||||
/// The interface the packet was received on
|
||||
pub interface_name: String,
|
||||
/// The internet protocol type of the packet
|
||||
pub internet_protocol: IpType,
|
||||
/// The hostname / service name
|
||||
pub hostname: String,
|
||||
/// The type of the service
|
||||
pub service_type: String,
|
||||
/// The domain of the service
|
||||
pub domain: String,
|
||||
/// Additional service metadata
|
||||
pub metadata: Vec<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for MdnsPacket {
|
||||
type Error = PacketParseError;
|
||||
|
||||
/// Parses a packet from the `avahi-browse` subprocess
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
// Convert the incoming data into an iter over its components
|
||||
let mut iter = s.split(';');
|
||||
|
||||
Ok(Self {
|
||||
// Grabs the first char of the first arg, and reads it as a mode
|
||||
mode: PacketMode::try_from(
|
||||
try_unwrap_arg(iter.next())?
|
||||
.chars()
|
||||
.next()
|
||||
.ok_or(PacketParseError::NotEnoughArgsError)?,
|
||||
)?,
|
||||
// Grabs the second arg, and reads it as an interface name
|
||||
interface_name: try_unwrap_arg(iter.next())?.to_string(),
|
||||
// Grabs the third arg, and reads it as an internet protocol type
|
||||
internet_protocol: IpType::try_from(try_unwrap_arg(iter.next())?.to_string())?,
|
||||
// Grabs the fourth arg, and reads it as a hostname
|
||||
hostname: try_unwrap_arg(iter.next())?.to_string(),
|
||||
// Grabs the fifth arg, and reads it as a service type
|
||||
service_type: try_unwrap_arg(iter.next())?.to_string(),
|
||||
// Grabs the sixth arg, and reads it as a domain
|
||||
domain: try_unwrap_arg(iter.next())?.to_string(),
|
||||
// Grabs the remaining args, and reads them as metadata
|
||||
metadata: iter.map(|s| s.to_string()).collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulates everything needed to read from and control an `avahi-browse` subprocess
|
||||
#[derive(Debug)]
|
||||
pub struct AvahiSubprocess {
|
||||
thread: JoinHandle<()>,
|
||||
|
||||
/// This stream sends parsed packets back to the main thread
|
||||
pub packet_stream: Receiver<MdnsPacket>,
|
||||
}
|
||||
|
||||
impl AvahiSubprocess {
|
||||
/// Creates a new `avahi-browse` subprocess
|
||||
pub fn spawn() -> Self {
|
||||
// Create a channel to send parsed packets back to the main thread
|
||||
let (packet_sender, packet_receiver) = mpsc::channel::<MdnsPacket>();
|
||||
|
||||
let thread = thread::spawn(move || {
|
||||
// Spawn avahi-browse
|
||||
let child = std::process::Command::new("avahi-browse")
|
||||
.arg("-p")
|
||||
.arg("-r")
|
||||
.arg("-a")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn avahi-browse subprocess");
|
||||
|
||||
// Create a stream from the stdout of the child
|
||||
let stream = child
|
||||
.stdout
|
||||
.expect("Failed to get stdout of avahi-browse subprocess");
|
||||
let stream = std::io::BufReader::new(stream);
|
||||
let stream = stream.lines();
|
||||
|
||||
// Parse the lines from the stream
|
||||
for line in stream {
|
||||
let line = line.expect("Failed to read line from avahi-browse subprocess");
|
||||
|
||||
match MdnsPacket::try_from(line.to_string()) {
|
||||
Ok(packet) => {
|
||||
// Send the packet to the main thread
|
||||
println!("{:?}", packet);
|
||||
packet_sender
|
||||
.send(packet)
|
||||
.expect("Failed to send packet to main thread");
|
||||
}
|
||||
Err(e) => {
|
||||
// Log the error
|
||||
eprintln!("Failed to parse packet: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Return the subprocess
|
||||
Self {
|
||||
thread,
|
||||
packet_stream: packet_receiver,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
@ -64,3 +63,21 @@
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
)]
|
||||
|
||||
use colored::Colorize;
|
||||
mod ipc;
|
||||
|
||||
fn main() {
|
||||
|
||||
// Check for avahi-browse on this system
|
||||
if let Err(_) = which::which("avahi-browse") {
|
||||
eprintln!("{}", "`avahi-browse` was not found in system $PATH".red());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Spawn the subprocess
|
||||
let subprocess = ipc::AvahiSubprocess::spawn();
|
||||
|
||||
loop{}
|
||||
|
||||
}
|
Reference in New Issue
Block a user