cargo fmt

main
_ 2025-03-21 12:21:19 -05:00
parent dc39531dc6
commit 319d8e6d29
9 changed files with 1009 additions and 1095 deletions

View File

@ -1,73 +1,72 @@
use crate::prelude::*; use crate::prelude::*;
pub const LOOKAROUND_VERSION: &str = env! ("CARGO_PKG_VERSION"); pub const LOOKAROUND_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn find_project_dirs () -> Option <ProjectDirs> { pub fn find_project_dirs() -> Option<ProjectDirs> {
ProjectDirs::from ("", "ReactorScram", "LookAround") ProjectDirs::from("", "ReactorScram", "LookAround")
} }
#[derive (Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum AppError { pub enum AppError {
#[error (transparent)] #[error(transparent)]
AddrParse (#[from] std::net::AddrParseError), AddrParse(#[from] std::net::AddrParseError),
#[error (transparent)] #[error(transparent)]
CliArgs (#[from] CliArgError), CliArgs(#[from] CliArgError),
#[error ("Operation timed out")] #[error("Operation timed out")]
Elapsed (#[from] tokio::time::error::Elapsed), Elapsed(#[from] tokio::time::error::Elapsed),
#[error (transparent)] #[error(transparent)]
Io (#[from] std::io::Error), Io(#[from] std::io::Error),
#[error (transparent)] #[error(transparent)]
Ip (#[from] crate::ip::IpError), Ip(#[from] crate::ip::IpError),
#[error (transparent)] #[error(transparent)]
Join (#[from] tokio::task::JoinError), Join(#[from] tokio::task::JoinError),
#[error (transparent)] #[error(transparent)]
MacAddr (#[from] mac_address::MacAddressError), MacAddr(#[from] mac_address::MacAddressError),
#[error (transparent)] #[error(transparent)]
Message (#[from] crate::message::MessageError), Message(#[from] crate::message::MessageError),
#[error (transparent)] #[error(transparent)]
ParseInt (#[from] std::num::ParseIntError), ParseInt(#[from] std::num::ParseIntError),
#[error (transparent)] #[error(transparent)]
Tlv (#[from] crate::tlv::TlvError), Tlv(#[from] crate::tlv::TlvError),
} }
#[derive (Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum CliArgError { pub enum CliArgError {
#[error ("Missing value for argument `{0}`")] #[error("Missing value for argument `{0}`")]
MissingArgumentValue (String), MissingArgumentValue(String),
#[error ("Missing required argument <{0}>")] #[error("Missing required argument <{0}>")]
MissingRequiredArg (String), MissingRequiredArg(String),
#[error ("First argument should be a subcommand")] #[error("First argument should be a subcommand")]
MissingSubcommand, MissingSubcommand,
#[error ("Unknown subcommand `{0}`")] #[error("Unknown subcommand `{0}`")]
UnknownSubcommand (String), UnknownSubcommand(String),
#[error ("Unrecognized argument `{0}`")] #[error("Unrecognized argument `{0}`")]
UnrecognizedArgument (String), UnrecognizedArgument(String),
} }
pub async fn recv_msg_from (socket: &UdpSocket) -> Result <(Vec <Message>, SocketAddr), AppError> pub async fn recv_msg_from(socket: &UdpSocket) -> Result<(Vec<Message>, SocketAddr), AppError> {
{ let mut buf = vec![0u8; PACKET_SIZE];
let mut buf = vec! [0u8; PACKET_SIZE]; let (bytes_recved, remote_addr) = socket.recv_from(&mut buf).await?;
let (bytes_recved, remote_addr) = socket.recv_from (&mut buf).await?; buf.truncate(bytes_recved);
buf.truncate (bytes_recved); let msgs = Message::from_slice2(&buf)?;
let msgs = Message::from_slice2 (&buf)?;
Ok((msgs, remote_addr))
Ok ((msgs, remote_addr))
} }
#[derive (Clone)] #[derive(Clone)]
pub struct Params { pub struct Params {
// Servers bind on this port, clients must send to the port // Servers bind on this port, clients must send to the port
pub server_port: u16, pub server_port: u16,
// Clients and servers will all join the same multicast addr // Clients and servers will all join the same multicast addr
pub multicast_addr: Ipv4Addr, pub multicast_addr: Ipv4Addr,
} }
impl Default for Params { impl Default for Params {
fn default () -> Self { fn default() -> Self {
Self { Self {
server_port: 9040, server_port: 9040,
multicast_addr: Ipv4Addr::new (225, 100, 99, 98), multicast_addr: Ipv4Addr::new(225, 100, 99, 98),
} }
} }
} }

View File

@ -1,40 +1,26 @@
type Mac = [u8; 6]; type Mac = [u8; 6];
pub fn debug () { pub fn debug() {
for input in [ for input in [
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1],
] { ] {
assert_eq! (unmix (mix (input)), input); assert_eq!(unmix(mix(input)), input);
} }
println! ("Passed"); println!("Passed");
} }
// NOT intended for any cryptography or security. This is TRIVIALLY reversible. // NOT intended for any cryptography or security. This is TRIVIALLY reversible.
// It's just to make it easier for humans to tell apart MACs where only a couple // It's just to make it easier for humans to tell apart MACs where only a couple
// numbers differ. // numbers differ.
fn mix (i: Mac) -> Mac { fn mix(i: Mac) -> Mac {
[ [i[0] ^ i[5], i[1] ^ i[4], i[2] ^ i[3], i[3], i[4], i[5]]
i [0] ^ i [5],
i [1] ^ i [4],
i [2] ^ i [3],
i [3],
i [4],
i [5],
]
} }
fn unmix (i: Mac) -> Mac { fn unmix(i: Mac) -> Mac {
[ [i[0] ^ i[5], i[1] ^ i[4], i[2] ^ i[3], i[3], i[4], i[5]]
i [0] ^ i [5],
i [1] ^ i [4],
i [2] ^ i [3],
i [3],
i [4],
i [5],
]
} }

View File

@ -1,300 +1,310 @@
use crate::prelude::*; use crate::prelude::*;
struct ServerResponse { struct ServerResponse {
mac: Option <[u8; 6]>, mac: Option<[u8; 6]>,
nickname: Option <String>, nickname: Option<String>,
} }
struct ConfigFile { struct ConfigFile {
nicknames: HashMap <String, String>, nicknames: HashMap<String, String>,
} }
struct ClientParams { struct ClientParams {
common: app_common::Params, common: app_common::Params,
bind_addrs: Vec <Ipv4Addr>, bind_addrs: Vec<Ipv4Addr>,
nicknames: HashMap <String, String>, nicknames: HashMap<String, String>,
timeout_ms: u64, timeout_ms: u64,
} }
pub async fn client <I: Iterator <Item=String>> (args: I) -> Result <(), AppError> { pub async fn client<I: Iterator<Item = String>>(args: I) -> Result<(), AppError> {
match get_mac_address() { match get_mac_address() {
Ok(Some(ma)) => { Ok(Some(ma)) => {
println!("Our MAC addr = {}", ma); println!("Our MAC addr = {}", ma);
} }
Ok(None) => println!("No MAC address found."), Ok(None) => println!("No MAC address found."),
Err(e) => println!("{:?}", e), Err(e) => println!("{:?}", e),
} }
let params = configure_client (args)?; let params = configure_client(args)?;
let socket = make_socket (&params.common, params.bind_addrs).await?; let socket = make_socket(&params.common, params.bind_addrs).await?;
let msg = Message::new_request1 ().to_vec ()?; let msg = Message::new_request1().to_vec()?;
tokio::spawn (send_requests (Arc::clone (&socket), params.common, msg)); tokio::spawn(send_requests(Arc::clone(&socket), params.common, msg));
let mut peers = HashMap::with_capacity (10); let mut peers = HashMap::with_capacity(10);
timeout (Duration::from_millis (params.timeout_ms), listen_for_responses (&socket, params.nicknames, &mut peers)).await.ok (); timeout(
Duration::from_millis(params.timeout_ms),
let mut peers: Vec <_> = peers.into_iter ().collect (); listen_for_responses(&socket, params.nicknames, &mut peers),
peers.sort_by_key (|(_, v)| v.mac); )
.await
println! ("Found {} peers:", peers.len ()); .ok();
for (ip, resp) in peers.into_iter () {
let mac = match resp.mac { let mut peers: Vec<_> = peers.into_iter().collect();
None => { peers.sort_by_key(|(_, v)| v.mac);
println! ("<Unknown> = {}", ip);
continue; println!("Found {} peers:", peers.len());
}, for (ip, resp) in peers.into_iter() {
Some (x) => x, let mac = match resp.mac {
}; None => {
println!("<Unknown> = {}", ip);
let nickname = match resp.nickname { continue;
None => { }
println! ("{} = {}", MacAddress::new (mac), ip.ip ()); Some(x) => x,
continue; };
},
Some (x) => x, let nickname = match resp.nickname {
}; None => {
println!("{} = {}", MacAddress::new(mac), ip.ip());
println! ("{} = {} `{}`", MacAddress::new (mac), ip.ip (), nickname); continue;
} }
Some(x) => x,
Ok (()) };
println!("{} = {} `{}`", MacAddress::new(mac), ip.ip(), nickname);
}
Ok(())
} }
pub async fn find_nick <I: Iterator <Item=String>> (mut args: I) -> Result <(), AppError> pub async fn find_nick<I: Iterator<Item = String>>(mut args: I) -> Result<(), AppError> {
{ let mut nick = None;
let mut nick = None; let mut timeout_ms = 500;
let mut timeout_ms = 500; let ConfigFile { nicknames } = load_config_file();
let ConfigFile {
nicknames, while let Some(arg) = args.next() {
} = load_config_file (); match arg.as_str() {
"--timeout-ms" => {
while let Some (arg) = args.next () { timeout_ms = match args.next() {
match arg.as_str () { None => return Err(CliArgError::MissingArgumentValue(arg).into()),
"--timeout-ms" => { Some(x) => u64::from_str(&x)?,
timeout_ms = match args.next () { };
None => return Err (CliArgError::MissingArgumentValue (arg).into ()), }
Some (x) => u64::from_str (&x)?, _ => nick = Some(arg),
}; }
}, }
_ => nick = Some (arg),
} let needle_nick =
} nick.ok_or_else(|| CliArgError::MissingRequiredArg("nickname".to_string()))?;
let needle_nick = Some(needle_nick);
let needle_nick = nick.ok_or_else (|| CliArgError::MissingRequiredArg ("nickname".to_string ()))?;
let needle_nick = Some (needle_nick); let common_params = Default::default();
let common_params = Default::default (); let socket = make_socket(&common_params, get_ips()?).await?;
let msg = Message::new_request1().to_vec()?;
let socket = make_socket (&common_params, get_ips ()?).await?; tokio::spawn(send_requests(Arc::clone(&socket), common_params, msg));
let msg = Message::new_request1 ().to_vec ()?;
tokio::spawn (send_requests (Arc::clone (&socket), common_params, msg)); timeout(Duration::from_millis(timeout_ms), async move {
loop {
timeout (Duration::from_millis (timeout_ms), async move { loop { let (msgs, remote_addr) = match recv_msg_from(&socket).await {
let (msgs, remote_addr) = match recv_msg_from (&socket).await { Err(_) => continue,
Err (_) => continue, Ok(x) => x,
Ok (x) => x, };
};
let mut resp = ServerResponse {
let mut resp = ServerResponse { mac: None,
mac: None, nickname: None,
nickname: None, };
};
for msg in msgs.into_iter() {
for msg in msgs.into_iter () { match msg {
match msg { Message::Response1(x) => resp.mac = x,
Message::Response1 (x) => resp.mac = x, Message::Response2(x) => resp.nickname = Some(x.nickname),
Message::Response2 (x) => resp.nickname = Some (x.nickname), _ => (),
_ => (), }
} }
}
resp.nickname = get_peer_nickname(&nicknames, resp.mac, resp.nickname);
resp.nickname = get_peer_nickname (&nicknames, resp.mac, resp.nickname);
if resp.nickname == needle_nick {
if resp.nickname == needle_nick { println!("{}", remote_addr.ip());
println! ("{}", remote_addr.ip ()); return;
return; }
} }
}}).await?; })
.await?;
Ok (())
Ok(())
} }
fn configure_client <I: Iterator <Item=String>> (mut args: I) fn configure_client<I: Iterator<Item = String>>(mut args: I) -> Result<ClientParams, AppError> {
-> Result <ClientParams, AppError> let mut bind_addrs = vec![];
{ let mut timeout_ms = 500;
let mut bind_addrs = vec! [];
let mut timeout_ms = 500; let ConfigFile { nicknames } = load_config_file();
let ConfigFile { while let Some(arg) = args.next() {
nicknames, match arg.as_str() {
} = load_config_file (); "--bind-addr" => {
bind_addrs.push(match args.next() {
while let Some (arg) = args.next () { None => return Err(CliArgError::MissingArgumentValue(arg).into()),
match arg.as_str () { Some(x) => Ipv4Addr::from_str(&x)?,
"--bind-addr" => { });
bind_addrs.push (match args.next () { }
None => return Err (CliArgError::MissingArgumentValue (arg).into ()), "--timeout-ms" => {
Some (x) => Ipv4Addr::from_str (&x)?, timeout_ms = match args.next() {
}); None => return Err(CliArgError::MissingArgumentValue(arg).into()),
}, Some(x) => u64::from_str(&x)?,
"--timeout-ms" => { };
timeout_ms = match args.next () { }
None => return Err (CliArgError::MissingArgumentValue (arg).into ()), _ => return Err(CliArgError::UnrecognizedArgument(arg).into()),
Some (x) => u64::from_str (&x)?, }
}; }
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()), if bind_addrs.is_empty() {
} bind_addrs = get_ips()?;
} }
if bind_addrs.is_empty () { Ok(ClientParams {
bind_addrs = get_ips ()?; common: Default::default(),
} bind_addrs,
nicknames,
Ok (ClientParams { timeout_ms,
common: Default::default (), })
bind_addrs,
nicknames,
timeout_ms,
})
} }
fn load_config_file () -> ConfigFile { fn load_config_file() -> ConfigFile {
let mut nicknames: HashMap <String, String> = Default::default (); let mut nicknames: HashMap<String, String> = Default::default();
if let Some (proj_dirs) = find_project_dirs () { if let Some(proj_dirs) = find_project_dirs() {
let mut ini = Ini::new_cs (); let mut ini = Ini::new_cs();
let path = proj_dirs.config_local_dir ().join ("client.ini"); let path = proj_dirs.config_local_dir().join("client.ini");
if ini.load (&path).is_ok () { if ini.load(&path).is_ok() {
let map_ref = ini.get_map_ref (); let map_ref = ini.get_map_ref();
if let Some (x) = map_ref.get ("nicknames") { if let Some(x) = map_ref.get("nicknames") {
for (k, v) in x { for (k, v) in x {
if let Some (v) = v { if let Some(v) = v {
let k = k.replace ('-', ":"); let k = k.replace('-', ":");
nicknames.insert (k, v.to_string ()); nicknames.insert(k, v.to_string());
} }
} }
} }
} }
} }
ConfigFile { ConfigFile { nicknames }
nicknames,
}
} }
async fn make_socket ( async fn make_socket(
common_params: &app_common::Params, common_params: &app_common::Params,
bind_addrs: Vec <Ipv4Addr>, bind_addrs: Vec<Ipv4Addr>,
) -> Result <Arc <UdpSocket>, AppError> { ) -> Result<Arc<UdpSocket>, AppError> {
let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, 0)).await?; let socket = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)).await?;
for bind_addr in &bind_addrs { for bind_addr in &bind_addrs {
if let Err (e) = socket.join_multicast_v4 (common_params.multicast_addr, *bind_addr) { if let Err(e) = socket.join_multicast_v4(common_params.multicast_addr, *bind_addr) {
println! ("Error joining multicast group with iface {}: {:?}", bind_addr, e); println!(
} "Error joining multicast group with iface {}: {:?}",
} bind_addr, e
);
Ok (Arc::new (socket)) }
}
Ok(Arc::new(socket))
} }
async fn send_requests ( async fn send_requests(
socket: Arc <UdpSocket>, socket: Arc<UdpSocket>,
params: app_common::Params, params: app_common::Params,
msg: Vec <u8>, msg: Vec<u8>,
) ) -> Result<(), AppError> {
-> Result <(), AppError> for _ in 0..10 {
{ socket
for _ in 0..10 { .send_to(&msg, (params.multicast_addr, params.server_port))
socket.send_to (&msg, (params.multicast_addr, params.server_port)).await?; .await?;
sleep (Duration::from_millis (100)).await; sleep(Duration::from_millis(100)).await;
} }
Ok::<_, AppError> (()) Ok::<_, AppError>(())
} }
async fn listen_for_responses ( async fn listen_for_responses(
socket: &UdpSocket, socket: &UdpSocket,
nicknames: HashMap <String, String>, nicknames: HashMap<String, String>,
peers: &mut HashMap <SocketAddr, ServerResponse> peers: &mut HashMap<SocketAddr, ServerResponse>,
) { ) {
loop { loop {
let (msgs, remote_addr) = match recv_msg_from (socket).await { let (msgs, remote_addr) = match recv_msg_from(socket).await {
Err (_) => continue, Err(_) => continue,
Ok (x) => x, Ok(x) => x,
}; };
let mut resp = ServerResponse { let mut resp = ServerResponse {
mac: None, mac: None,
nickname: None, nickname: None,
}; };
for msg in msgs.into_iter () { for msg in msgs.into_iter() {
match msg { match msg {
Message::Response1 (x) => resp.mac = x, Message::Response1(x) => resp.mac = x,
Message::Response2 (x) => resp.nickname = Some (x.nickname), Message::Response2(x) => resp.nickname = Some(x.nickname),
_ => (), _ => (),
} }
} }
resp.nickname = get_peer_nickname (&nicknames, resp.mac, resp.nickname); resp.nickname = get_peer_nickname(&nicknames, resp.mac, resp.nickname);
peers.insert (remote_addr, resp); peers.insert(remote_addr, resp);
} }
} }
fn get_peer_nickname ( fn get_peer_nickname(
nicknames: &HashMap <String, String>, nicknames: &HashMap<String, String>,
mac: Option <[u8; 6]>, mac: Option<[u8; 6]>,
peer_nickname: Option <String> peer_nickname: Option<String>,
) -> Option <String> ) -> Option<String> {
{ match peer_nickname.as_deref() {
match peer_nickname.as_deref () { None => (),
None => (), Some("") => (),
Some ("") => (), _ => return peer_nickname,
_ => return peer_nickname, }
}
if let Some(mac) = &mac {
if let Some (mac) = &mac { return nicknames
return nicknames.get (&format! ("{}", MacAddress::new (*mac))).cloned () .get(&format!("{}", MacAddress::new(*mac)))
} .cloned();
}
None
None
} }
#[cfg (test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[test] #[test]
fn test_nicknames () { fn test_nicknames() {
let mut nicks = HashMap::new (); let mut nicks = HashMap::new();
for (k, v) in [ for (k, v) in [("01:01:01:01:01:01", "phoenix")] {
("01:01:01:01:01:01", "phoenix") nicks.insert(k.to_string(), v.to_string());
] { }
nicks.insert (k.to_string (), v.to_string ());
} for (num, (mac, peer_nickname), expected) in [
// Somehow the server returns no MAC nor nick. In this case we are helpless
for (num, (mac, peer_nickname), expected) in [ (1, (None, None), None),
// Somehow the server returns no MAC nor nick. In this case we are helpless // If the server tells us its MAC, we can look up our nickname for it
( 1, (None, None), None), (2, (Some([1, 1, 1, 1, 1, 1]), None), Some("phoenix")),
// If the server tells us its MAC, we can look up our nickname for it // Unless it's not in our nick list.
( 2, (Some ([1, 1, 1, 1, 1, 1]), None), Some ("phoenix")), (3, (Some([1, 1, 1, 1, 1, 2]), None), None),
// Unless it's not in our nick list. // If the server tells us its nickname, that always takes priority
( 3, (Some ([1, 1, 1, 1, 1, 2]), None), None), (4, (None, Some("snowflake")), Some("snowflake")),
// If the server tells us its nickname, that always takes priority (
( 4, (None, Some ("snowflake")), Some ("snowflake")), 5,
( 5, (Some ([1, 1, 1, 1, 1, 1]), Some ("snowflake")), Some ("snowflake")), (Some([1, 1, 1, 1, 1, 1]), Some("snowflake")),
( 6, (Some ([1, 1, 1, 1, 1, 2]), Some ("snowflake")), Some ("snowflake")), Some("snowflake"),
// But blank nicknames are treated like None ),
( 7, (None, Some ("")), None), (
( 8, (Some ([1, 1, 1, 1, 1, 1]), Some ("")), Some ("phoenix")), 6,
( 9, (Some ([1, 1, 1, 1, 1, 2]), Some ("")), None), (Some([1, 1, 1, 1, 1, 2]), Some("snowflake")),
] { Some("snowflake"),
let actual = get_peer_nickname (&nicks, mac, peer_nickname.map (str::to_string)); ),
assert_eq! (actual.as_ref ().map (String::as_str), expected, "{}", num); // But blank nicknames are treated like None
} (7, (None, Some("")), None),
} (8, (Some([1, 1, 1, 1, 1, 1]), Some("")), Some("phoenix")),
(9, (Some([1, 1, 1, 1, 1, 2]), Some("")), None),
] {
let actual = get_peer_nickname(&nicks, mac, peer_nickname.map(str::to_string));
assert_eq!(actual.as_ref().map(String::as_str), expected, "{}", num);
}
}
} }

184
src/ip.rs
View File

@ -1,122 +1,112 @@
use std::{ use std::{net::Ipv4Addr, process::Command, str::FromStr};
net::Ipv4Addr,
process::Command,
str::FromStr,
};
#[derive (Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum IpError { pub enum IpError {
#[error (transparent)] #[error(transparent)]
Io (#[from] std::io::Error), Io(#[from] std::io::Error),
#[error (transparent)] #[error(transparent)]
FromUtf8 (#[from] std::string::FromUtf8Error), FromUtf8(#[from] std::string::FromUtf8Error),
#[error ("Self-IP detection is not implemented on Mac OS")] #[error("Self-IP detection is not implemented on Mac OS")]
NotImplementedOnMac, NotImplementedOnMac,
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> { pub fn get_ips() -> Result<Vec<Ipv4Addr>, IpError> {
let output = linux::get_ip_addr_output ()?; let output = linux::get_ip_addr_output()?;
Ok (linux::parse_ip_addr_output (&output)) Ok(linux::parse_ip_addr_output(&output))
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> { pub fn get_ips() -> Result<Vec<Ipv4Addr>, IpError> {
Err (IpError::NotImplementedOnMac) Err(IpError::NotImplementedOnMac)
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> { pub fn get_ips() -> Result<Vec<Ipv4Addr>, IpError> {
let output = windows::get_ip_config_output ()?; let output = windows::get_ip_config_output()?;
Ok (windows::parse_ip_config_output (&output)) Ok(windows::parse_ip_config_output(&output))
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub mod linux { pub mod linux {
use super::*; use super::*;
pub fn get_ip_addr_output () -> Result <String, IpError> {
let output = Command::new ("ip")
.arg ("addr")
.output ()?;
let output = output.stdout.as_slice ();
let output = String::from_utf8 (output.to_vec ())?;
Ok (output)
}
pub fn parse_ip_addr_output (output: &str) -> Vec <Ipv4Addr> { pub fn get_ip_addr_output() -> Result<String, IpError> {
// I wrote this in FP style because I was bored. let output = Command::new("ip").arg("addr").output()?;
let output = output.stdout.as_slice();
output.lines () let output = String::from_utf8(output.to_vec())?;
.map (|l| l.trim_start ()) Ok(output)
.filter_map (|l| l.strip_prefix ("inet ")) }
.filter_map (|l| l.find ('/').map (|x| &l [0..x]))
.filter_map (|l| Ipv4Addr::from_str (l).ok ()) pub fn parse_ip_addr_output(output: &str) -> Vec<Ipv4Addr> {
.filter (|a| ! a.is_loopback ()) // I wrote this in FP style because I was bored.
.collect ()
} output
.lines()
.map(|l| l.trim_start())
.filter_map(|l| l.strip_prefix("inet "))
.filter_map(|l| l.find('/').map(|x| &l[0..x]))
.filter_map(|l| Ipv4Addr::from_str(l).ok())
.filter(|a| !a.is_loopback())
.collect()
}
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub mod windows { pub mod windows {
use super::*; use super::*;
pub fn get_ip_config_output () -> Result <String, IpError> {
let output = Command::new ("ipconfig")
.output ()?;
let output = output.stdout.as_slice ();
let output = String::from_utf8 (output.to_vec ())?;
Ok (output)
}
pub fn parse_ip_config_output (output: &str) -> Vec <Ipv4Addr> { pub fn get_ip_config_output() -> Result<String, IpError> {
let mut addrs = vec! []; let output = Command::new("ipconfig").output()?;
let output = output.stdout.as_slice();
for line in output.lines () { let output = String::from_utf8(output.to_vec())?;
let line = line.trim_start (); Ok(output)
}
// Maybe only works on English locales?
if ! line.starts_with ("IPv4 Address") {
continue;
}
let colon_pos = match line.find (':') {
None => continue,
Some (x) => x,
};
let line = &line [colon_pos + 2..];
let addr = match Ipv4Addr::from_str (line) {
Err (_) => continue,
Ok (x) => x,
};
addrs.push (addr);
}
addrs
}
#[cfg (test)] pub fn parse_ip_config_output(output: &str) -> Vec<Ipv4Addr> {
mod test { let mut addrs = vec![];
use super::*;
for line in output.lines() {
#[test] let line = line.trim_start();
fn test () {
for (input, expected) in [ // Maybe only works on English locales?
( if !line.starts_with("IPv4 Address") {
r" continue;
}
let colon_pos = match line.find(':') {
None => continue,
Some(x) => x,
};
let line = &line[colon_pos + 2..];
let addr = match Ipv4Addr::from_str(line) {
Err(_) => continue,
Ok(x) => x,
};
addrs.push(addr);
}
addrs
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test() {
for (input, expected) in [(
r"
IPv4 Address . . .. . . . : 192.168.1.1 IPv4 Address . . .. . . . : 192.168.1.1
", ",
vec! [ vec![Ipv4Addr::new(192, 168, 1, 1)],
Ipv4Addr::new (192, 168, 1, 1), )] {
] let actual = parse_ip_config_output(input);
), assert_eq!(actual, expected);
] { }
let actual = parse_ip_config_output (input); }
assert_eq! (actual, expected); }
}
}
}
} }

View File

@ -9,53 +9,51 @@ mod prelude;
mod server; mod server;
pub mod tlv; pub mod tlv;
fn main () -> Result <(), AppError> { fn main() -> Result<(), AppError> {
let rt = tokio::runtime::Builder::new_current_thread () let rt = tokio::runtime::Builder::new_current_thread()
.enable_io () .enable_io()
.enable_time () .enable_time()
.build ()?; .build()?;
rt.block_on (async_main ())?; rt.block_on(async_main())?;
Ok (()) Ok(())
} }
async fn async_main () -> Result <(), AppError> { async fn async_main() -> Result<(), AppError> {
let mut args = env::args (); let mut args = env::args();
let _exe_name = args.next (); let _exe_name = args.next();
let subcommand: Option <String> = args.next (); let subcommand: Option<String> = args.next();
match subcommand.as_ref ().map (|x| &x[..]) { match subcommand.as_ref().map(|x| &x[..]) {
None => return Err (CliArgError::MissingSubcommand.into ()), None => return Err(CliArgError::MissingSubcommand.into()),
Some ("--version") => println! ("lookaround v{}", LOOKAROUND_VERSION), Some("--version") => println!("lookaround v{}", LOOKAROUND_VERSION),
Some ("client") => client::client (args).await?, Some("client") => client::client(args).await?,
Some ("config") => config (), Some("config") => config(),
Some ("debug-avalanche") => avalanche::debug (), Some("debug-avalanche") => avalanche::debug(),
Some ("find-nick") => client::find_nick (args).await?, Some("find-nick") => client::find_nick(args).await?,
Some ("my-ips") => my_ips ()?, Some("my-ips") => my_ips()?,
Some ("server") => server::server (args).await?, Some("server") => server::server(args).await?,
Some (x) => return Err (CliArgError::UnknownSubcommand (x.to_string ()).into ()), Some(x) => return Err(CliArgError::UnknownSubcommand(x.to_string()).into()),
} }
Ok (()) Ok(())
} }
fn config () { fn config() {
if let Some (proj_dirs) = ProjectDirs::from ("", "ReactorScram", "LookAround") { if let Some(proj_dirs) = ProjectDirs::from("", "ReactorScram", "LookAround") {
println! ("Using config dir {:?}", proj_dirs.config_local_dir ()); println!("Using config dir {:?}", proj_dirs.config_local_dir());
} } else {
else { println!("Can't detect config dir.");
println! ("Can't detect config dir."); }
}
} }
fn my_ips () -> Result <(), AppError> { fn my_ips() -> Result<(), AppError> {
for addr in ip::get_ips ()? for addr in ip::get_ips()? {
{ println!("{:?}", addr);
println! ("{:?}", addr); }
}
Ok(())
Ok (())
} }

View File

@ -5,343 +5,302 @@ pub const PACKET_SIZE: usize = 1024;
type Mac = [u8; 6]; type Mac = [u8; 6];
#[derive (Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Message { pub enum Message {
// 1 // 1
Request1 { Request1 { idem_id: [u8; 8], mac: Option<Mac> },
idem_id: [u8; 8], // 2
mac: Option <Mac> Response1(Option<Mac>),
}, // 3
// 2 Response2(Response2),
Response1 (Option <Mac>),
// 3
Response2 (Response2),
} }
impl Message { impl Message {
pub fn new_request1 () -> Message { pub fn new_request1() -> Message {
let mut idem_id = [0u8; 8]; let mut idem_id = [0u8; 8];
rand::thread_rng ().fill_bytes (&mut idem_id); rand::thread_rng().fill_bytes(&mut idem_id);
Message::Request1 { Message::Request1 { idem_id, mac: None }
idem_id, }
mac: None,
}
}
} }
#[derive (Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Response2 { pub struct Response2 {
pub idem_id: [u8; 8], pub idem_id: [u8; 8],
pub nickname: String, pub nickname: String,
} }
#[derive (Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum MessageError { pub enum MessageError {
#[error (transparent)] #[error(transparent)]
Io (#[from] std::io::Error), Io(#[from] std::io::Error),
#[error ("Length prefix too long")] #[error("Length prefix too long")]
LengthPrefixTooLong ((usize, usize)), LengthPrefixTooLong((usize, usize)),
#[error (transparent)] #[error(transparent)]
Tlv (#[from] tlv::TlvError), Tlv(#[from] tlv::TlvError),
#[error (transparent)] #[error(transparent)]
TryFromInt (#[from] std::num::TryFromIntError), TryFromInt(#[from] std::num::TryFromIntError),
#[error ("Unknown type")] #[error("Unknown type")]
UnknownType, UnknownType,
#[error (transparent)] #[error(transparent)]
FromUtf8 (#[from] std::string::FromUtf8Error), FromUtf8(#[from] std::string::FromUtf8Error),
} }
#[derive (Default)] #[derive(Default)]
struct DummyWriter { struct DummyWriter {
position: usize, position: usize,
} }
impl Write for DummyWriter { impl Write for DummyWriter {
fn flush (&mut self) -> std::io::Result <()> { fn flush(&mut self) -> std::io::Result<()> {
Ok (()) Ok(())
} }
fn write (&mut self, buf: &[u8]) -> std::io::Result <usize> { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.position += buf.len (); self.position += buf.len();
Ok (buf.len ()) Ok(buf.len())
} }
} }
impl Message { impl Message {
pub fn write <T> (&self, w: &mut Cursor <T>) -> Result <(), MessageError> pub fn write<T>(&self, w: &mut Cursor<T>) -> Result<(), MessageError>
where Cursor <T>: Write where
{ Cursor<T>: Write,
match self { {
Self::Request1 { match self {
idem_id, Self::Request1 { idem_id, mac } => {
mac, w.write_all(&[1])?;
}=> { w.write_all(&idem_id[..])?;
w.write_all (&[1])?; Self::write_mac_opt(w, *mac)?;
w.write_all (&idem_id[..])?; }
Self::write_mac_opt (w, *mac)?; Self::Response1(mac) => {
}, w.write_all(&[2])?;
Self::Response1 (mac) => { Self::write_mac_opt(w, *mac)?;
w.write_all (&[2])?; }
Self::write_mac_opt (w, *mac)?; Self::Response2(x) => {
}, w.write_all(&[3])?;
Self::Response2 (x) => { // Measure length with dummy writes
w.write_all (&[3])?; // This is dumb, I'm just messing around to see if I can do
// Measure length with dummy writes // this without allocating.
// This is dumb, I'm just messing around to see if I can do let mut dummy_writer = DummyWriter::default();
// this without allocating.
let mut dummy_writer = DummyWriter::default (); Self::write_response_2(&mut dummy_writer, x)?;
Self::write_response_2 (&mut dummy_writer, x)?; // Write length and real params to real output
let len = u32::try_from(dummy_writer.position)?;
// Write length and real params to real output w.write_all(&len.to_le_bytes())?;
let len = u32::try_from (dummy_writer.position)?; Self::write_response_2(w, x)?;
w.write_all (&len.to_le_bytes ())?; }
Self::write_response_2 (w, x)?; }
},
} Ok(())
}
Ok (())
} fn write_response_2<W: Write>(w: &mut W, params: &Response2) -> Result<(), MessageError> {
w.write_all(&params.idem_id)?;
fn write_response_2 <W: Write> (w: &mut W, params: &Response2) let nickname = params.nickname.as_bytes();
-> Result <(), MessageError> tlv::Writer::<_>::lv_bytes(w, nickname)?;
{ Ok(())
w.write_all (&params.idem_id)?; }
let nickname = params.nickname.as_bytes ();
tlv::Writer::<_>::lv_bytes (w, nickname)?; fn write_mac_opt<W: Write>(w: &mut W, mac: Option<[u8; 6]>) -> Result<(), std::io::Error> {
Ok (()) match mac {
} Some(mac) => {
w.write_all(&[1])?;
fn write_mac_opt <W: Write> (w: &mut W, mac: Option <[u8; 6]>) -> Result <(), std::io::Error> w.write_all(&mac[..])?;
{ }
match mac { None => w.write_all(&[0])?,
Some (mac) => { }
w.write_all (&[1])?; Ok(())
w.write_all (&mac[..])?; }
},
None => w.write_all (&[0])?, pub fn to_vec(&self) -> Result<Vec<u8>, MessageError> {
} let mut cursor = Cursor::new(Vec::with_capacity(PACKET_SIZE));
Ok (()) cursor.write_all(&MAGIC_NUMBER)?;
} self.write(&mut cursor)?;
Ok(cursor.into_inner())
pub fn to_vec (&self) -> Result <Vec <u8>, MessageError> { }
let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE));
cursor.write_all (&MAGIC_NUMBER)?; pub fn many_to_vec(msgs: &[Self]) -> Result<Vec<u8>, MessageError> {
self.write (&mut cursor)?; let mut cursor = Cursor::new(Vec::with_capacity(PACKET_SIZE));
Ok (cursor.into_inner ()) cursor.write_all(&MAGIC_NUMBER)?;
} for msg in msgs {
msg.write(&mut cursor)?;
pub fn many_to_vec (msgs: &[Self]) -> Result <Vec <u8>, MessageError> { }
let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE)); Ok(cursor.into_inner())
cursor.write_all (&MAGIC_NUMBER)?; }
for msg in msgs {
msg.write (&mut cursor)?; fn read2<R: std::io::Read>(r: &mut R) -> Result<Self, MessageError> {
} let t = tlv::Reader::u8(r)?;
Ok (cursor.into_inner ())
} Ok(match t {
1 => {
fn read2 <R: std::io::Read> (r: &mut R) -> Result <Self, MessageError> { let mut idem_id = [0u8; 8];
let t = tlv::Reader::u8 (r)?; r.read_exact(&mut idem_id)?;
Ok (match t { let mac = Self::read_mac_opt(r)?;
1 => { Self::Request1 { idem_id, mac }
let mut idem_id = [0u8; 8]; }
r.read_exact (&mut idem_id)?; 2 => {
let mac = Self::read_mac_opt(r)?;
let mac = Self::read_mac_opt (r)?; Self::Response1(mac)
Self::Request1 { }
idem_id, 3 => {
mac, tlv::Reader::<_>::length(r)?;
}
}, let mut idem_id = [0; 8];
2 => { r.read_exact(&mut idem_id)?;
let mac = Self::read_mac_opt (r)?;
Self::Response1 (mac) let nickname = tlv::Reader::<_>::lv_bytes_to_vec(r, 64)?;
}, let nickname = String::from_utf8(nickname)?;
3 => {
tlv::Reader::<_>::length (r)?; Self::Response2(Response2 { idem_id, nickname })
}
let mut idem_id = [0; 8]; _ => return Err(MessageError::UnknownType),
r.read_exact (&mut idem_id)?; })
}
let nickname = tlv::Reader::<_>::lv_bytes_to_vec (r, 64)?;
let nickname = String::from_utf8 (nickname)?; fn read_mac_opt<R: std::io::Read>(r: &mut R) -> Result<Option<[u8; 6]>, std::io::Error> {
Ok(if tlv::Reader::u8(r)? == 1 {
Self::Response2 (Response2 { let mut mac = [0u8; 6];
idem_id, r.read_exact(&mut mac)?;
nickname, Some(mac)
}) } else {
}, None
_ => return Err (MessageError::UnknownType), })
}) }
}
pub fn from_slice2(buf: &[u8]) -> Result<Vec<Self>, MessageError> {
fn read_mac_opt <R: std::io::Read> (r: &mut R) let mut cursor = Cursor::new(buf);
-> Result <Option <[u8; 6]>, std::io::Error> tlv::Reader::expect(&mut cursor, &MAGIC_NUMBER)?;
{
Ok (if tlv::Reader::u8 (r)? == 1 { let mut msgs = Vec::with_capacity(2);
let mut mac = [0u8; 6];
r.read_exact (&mut mac)?; while cursor.position() < u64::try_from(buf.len())? {
Some (mac) let msg = Self::read2(&mut cursor)?;
} msgs.push(msg);
else { }
None Ok(msgs)
}) }
}
pub fn from_slice2 (buf: &[u8]) -> Result <Vec <Self>, MessageError> {
let mut cursor = Cursor::new (buf);
tlv::Reader::expect (&mut cursor, &MAGIC_NUMBER)?;
let mut msgs = Vec::with_capacity (2);
while cursor.position () < u64::try_from (buf.len ())? {
let msg = Self::read2 (&mut cursor)?;
msgs.push (msg);
}
Ok (msgs)
}
} }
#[cfg (test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[test] #[test]
fn test_write_2 () -> Result <(), MessageError> { fn test_write_2() -> Result<(), MessageError> {
for (input, expected) in [ for (input, expected) in [
( (
vec! [ vec![Message::Request1 {
Message::Request1 { idem_id: [1, 2, 3, 4, 5, 6, 7, 8],
idem_id: [1, 2, 3, 4, 5, 6, 7, 8,], mac: None,
mac: None, }],
}, vec![
], 154, 74, 67, 129, // Request tag
vec! [ 1, // Idem ID
154, 74, 67, 129, 1, 2, 3, 4, 5, 6, 7, 8, // MAC is None
// Request tag 0,
1, ],
// Idem ID ),
1, 2, 3, 4, 5, 6, 7, 8, (
// MAC is None vec![
0, Message::Response1(Some([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])),
], Message::Response2(Response2 {
), idem_id: [1, 2, 3, 4, 5, 6, 7, 8],
( nickname: ":V".to_string(),
vec! [ }),
Message::Response1 (Some ([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])), ],
Message::Response2 (Response2 { vec![
idem_id: [1, 2, 3, 4, 5, 6, 7, 8,], // Magic number for LookAround packets
nickname: ":V".to_string (), 154, 74, 67, 129, // Response1 tag
}), 2, // MAC is Some
], 1, // MAC
vec! [ 17, 34, 51, 68, 85, 102, // Response2 tag
// Magic number for LookAround packets 3, // Length prefix
154, 74, 67, 129, 14, 0, 0, 0, // Idem ID
// Response1 tag 1, 2, 3, 4, 5, 6, 7, 8, // Length-prefixed string
2, 2, 0, 0, 0, 58, 86,
// MAC is Some ],
1, ),
// MAC ] {
17, 34, 51, 68, 85, 102, let actual = Message::many_to_vec(&input)?;
// Response2 tag assert_eq!(actual, expected, "{:?}", input);
3, }
// Length prefix
14, 0, 0, 0, Ok(())
// Idem ID }
1, 2, 3, 4, 5, 6, 7, 8,
// Length-prefixed string #[test]
2, 0, 0, 0, fn test_write_1() -> Result<(), MessageError> {
58, 86, for (input, expected) in [
], (
), Message::Request1 {
] { idem_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
let actual = Message::many_to_vec (&input)?; mac: None,
assert_eq! (actual, expected, "{:?}", input); },
} vec![
154, 74, 67, 129, // Request tag
Ok (()) 1, // Idem ID
} 1, 2, 3, 4, 5, 6, 7, 8, // MAC is None
0,
#[test] ],
fn test_write_1 () -> Result <(), MessageError> { ),
for (input, expected) in [ (
( Message::Response1(Some([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])),
Message::Request1 { vec![
idem_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], // Magic number for LookAround packets
mac: None, 154, 74, 67, 129, // Response tag
}, 2, // MAC is Some
vec! [ 1, // MAC
154, 74, 67, 129, 17, 34, 51, 68, 85, 102,
// Request tag ],
1, ),
// Idem ID (
1, 2, 3, 4, 5, 6, 7, 8, Message::Response1(None),
// MAC is None vec![
0, // Magic number for LookAround packets
], 154, 74, 67, 129, // Response tag
), 2, // MAC is None
( 0,
Message::Response1 (Some ([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])), ],
vec! [ ),
// Magic number for LookAround packets ]
154, 74, 67, 129, .into_iter()
// Response tag {
2, let actual = input.to_vec()?;
// MAC is Some assert_eq!(actual, expected, "{:?}", input);
1, }
// MAC
17, 34, 51, 68, 85, 102, Ok(())
], }
),
( #[test]
Message::Response1 (None), fn test_read_2() -> Result<(), MessageError> {
vec! [ for input in [
// Magic number for LookAround packets vec![Message::Request1 {
154, 74, 67, 129, idem_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
// Response tag mac: None,
2, }],
// MAC is None vec![Message::Response1(Some([
0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
], ]))],
), vec![Message::Response1(None)],
].into_iter () { vec![
let actual = input.to_vec ()?; Message::Response1(Some([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])),
assert_eq! (actual, expected, "{:?}", input); Message::Response2(Response2 {
} idem_id: [1, 2, 3, 4, 5, 6, 7, 8],
nickname: ":V".to_string(),
Ok (()) }),
} ],
]
#[test] .into_iter()
fn test_read_2 () -> Result <(), MessageError> { {
for input in [ let encoded = Message::many_to_vec(&input)?;
vec! [ let decoded = Message::from_slice2(&encoded)?;
Message::Request1 { assert_eq!(input, decoded);
idem_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], }
mac: None,
}, Ok(())
], }
vec! [
Message::Response1 (Some ([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])),
],
vec! [
Message::Response1 (None),
],
vec! [
Message::Response1 (Some ([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])),
Message::Response2 (Response2 {
idem_id: [1, 2, 3, 4, 5, 6, 7, 8,],
nickname: ":V".to_string (),
}),
],
].into_iter () {
let encoded = Message::many_to_vec (&input)?;
let decoded = Message::from_slice2 (&encoded)?;
assert_eq! (input, decoded);
}
Ok (())
}
} }

View File

@ -1,51 +1,27 @@
pub use std::{ pub use std::{
collections::HashMap, collections::HashMap,
env, env,
io::{ io::{Cursor, Write},
Cursor, net::{Ipv4Addr, SocketAddr, SocketAddrV4},
Write, str::FromStr,
}, sync::Arc,
net::{ time::Duration,
Ipv4Addr,
SocketAddr,
SocketAddrV4,
},
str::FromStr,
sync::Arc,
time::{
Duration,
},
}; };
pub use configparser::ini::Ini; pub use configparser::ini::Ini;
pub use directories::ProjectDirs; pub use directories::ProjectDirs;
pub use mac_address::{ pub use mac_address::{MacAddress, get_mac_address};
MacAddress,
get_mac_address,
};
pub use rand::RngCore; pub use rand::RngCore;
pub use tokio::{ pub use tokio::{
net::UdpSocket, net::UdpSocket,
time::{ time::{sleep, timeout},
sleep,
timeout,
},
}; };
pub use crate::{ pub use crate::{
app_common::{ app_common::{
self, self, AppError, CliArgError, LOOKAROUND_VERSION, find_project_dirs, recv_msg_from,
LOOKAROUND_VERSION, },
AppError, ip::get_ips,
CliArgError, message::{self, Message, PACKET_SIZE},
find_project_dirs, tlv,
recv_msg_from,
},
ip::get_ips,
message::{
self,
PACKET_SIZE,
Message,
},
tlv,
}; };

View File

@ -1,148 +1,149 @@
use crate::prelude::*; use crate::prelude::*;
#[derive (Clone)] #[derive(Clone)]
struct Params { struct Params {
common: app_common::Params, common: app_common::Params,
bind_addrs: Vec <Ipv4Addr>, bind_addrs: Vec<Ipv4Addr>,
nickname: String, nickname: String,
our_mac: Option <[u8; 6]>, our_mac: Option<[u8; 6]>,
} }
pub async fn server <I: Iterator <Item=String>> (args: I) -> Result <(), AppError> pub async fn server<I: Iterator<Item = String>>(args: I) -> Result<(), AppError> {
{ match get_mac_address() {
match get_mac_address() { Ok(Some(ma)) => {
Ok(Some(ma)) => { println!("Our MAC addr = {}", ma);
println!("Our MAC addr = {}", ma); }
} Ok(None) => println!("No MAC address found."),
Ok(None) => println!("No MAC address found."), Err(e) => println!("{:?}", e),
Err(e) => println!("{:?}", e), }
}
let params = configure(args)?;
let params = configure (args)?;
let socket = UdpSocket::bind(SocketAddrV4::new(
let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, params.common.server_port)).await?; Ipv4Addr::UNSPECIFIED,
params.common.server_port,
for bind_addr in &params.bind_addrs { ))
if let Err (e) = socket.join_multicast_v4 (params.common.multicast_addr, *bind_addr) { .await?;
println! ("Error joining multicast group with iface {}: {:?}", bind_addr, e);
} for bind_addr in &params.bind_addrs {
} if let Err(e) = socket.join_multicast_v4(params.common.multicast_addr, *bind_addr) {
println!(
serve_interface (params, socket).await?; "Error joining multicast group with iface {}: {:?}",
bind_addr, e
Ok (()) );
}
}
serve_interface(params, socket).await?;
Ok(())
} }
fn configure <I: Iterator <Item=String>> (mut args: I) -> Result <Params, AppError> fn configure<I: Iterator<Item = String>>(mut args: I) -> Result<Params, AppError> {
{ let common = app_common::Params::default();
let common = app_common::Params::default (); let mut bind_addrs = vec![];
let mut bind_addrs = vec![]; let mut nickname = String::new();
let mut nickname = String::new ();
if let Some(proj_dirs) = find_project_dirs() {
if let Some (proj_dirs) = find_project_dirs () { let mut ini = Ini::new_cs();
let mut ini = Ini::new_cs (); let path = proj_dirs.config_local_dir().join("server.ini");
let path = proj_dirs.config_local_dir ().join ("server.ini"); if ini.load(&path).is_ok() {
if ini.load (&path).is_ok () { if let Some(x) = ini.get("server", "nickname") {
if let Some (x) = ini.get ("server", "nickname") { nickname = x;
nickname = x; eprintln!("Loaded nickname {:?}", nickname);
eprintln! ("Loaded nickname {:?}", nickname); }
} } else {
} eprintln!(
else { "Can't load ini from {:?}, didn't load default configs",
eprintln! ("Can't load ini from {:?}, didn't load default configs", path); path
} );
} }
else { } else {
eprintln! ("Can't find config dir, didn't load default configs"); eprintln!("Can't find config dir, didn't load default configs");
} }
while let Some (arg) = args.next () { while let Some(arg) = args.next() {
match arg.as_str () { match arg.as_str() {
"--bind-addr" => { "--bind-addr" => {
bind_addrs.push (match args.next () { bind_addrs.push(match args.next() {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()), None => return Err(CliArgError::MissingArgumentValue(arg).into()),
Some (x) => Ipv4Addr::from_str (&x)?, Some(x) => Ipv4Addr::from_str(&x)?,
}); });
}, }
"--nickname" => { "--nickname" => {
nickname = match args.next () { nickname = match args.next() {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()), None => return Err(CliArgError::MissingArgumentValue(arg).into()),
Some (x) => x Some(x) => x,
}; };
}, }
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()), _ => return Err(CliArgError::UnrecognizedArgument(arg).into()),
} }
} }
let our_mac = get_mac_address ()?.map (|x| x.bytes ()); let our_mac = get_mac_address()?.map(|x| x.bytes());
if our_mac.is_none () { if our_mac.is_none() {
println! ("Warning: Can't find our own MAC address. We won't be able to respond to MAC-specific lookaround requests"); println!(
} "Warning: Can't find our own MAC address. We won't be able to respond to MAC-specific lookaround requests"
);
if bind_addrs.is_empty () { }
println! ("No bind addresses given, auto-detecting all local IPs");
bind_addrs = get_ips ()?; if bind_addrs.is_empty() {
} println!("No bind addresses given, auto-detecting all local IPs");
bind_addrs = get_ips()?;
Ok (Params { }
common,
bind_addrs, Ok(Params {
nickname, common,
our_mac, bind_addrs,
}) nickname,
our_mac,
})
} }
async fn serve_interface ( async fn serve_interface(params: Params, socket: UdpSocket) -> Result<(), AppError> {
params: Params, let mut recent_idem_ids = Vec::with_capacity(32);
socket: UdpSocket,
) loop {
-> Result <(), AppError> println!("Listening...");
{ let (req_msgs, remote_addr) = match recv_msg_from(&socket).await {
let mut recent_idem_ids = Vec::with_capacity (32); Ok(x) => x,
Err(e) => {
loop { println!("Error while receiving message: {:?}", e);
println! ("Listening..."); continue;
let (req_msgs, remote_addr) = match recv_msg_from (&socket).await { }
Ok (x) => x, };
Err (e) => {
println! ("Error while receiving message: {:?}", e); let req = match req_msgs.into_iter().next() {
continue; Some(x) => x,
}, _ => {
}; println!("Don't know how to handle this message, ignoring");
continue;
let req = match req_msgs.into_iter ().next () { }
Some (x) => x, };
_ => {
println! ("Don't know how to handle this message, ignoring"); let resp = match req {
continue; Message::Request1 { mac: None, idem_id } => {
}, if recent_idem_ids.contains(&idem_id) {
}; None
} else {
let resp = match req { recent_idem_ids.insert(0, idem_id);
Message::Request1 { recent_idem_ids.truncate(30);
mac: None, Some(vec![
idem_id, Message::Response1(params.our_mac),
} => { Message::Response2(message::Response2 {
if recent_idem_ids.contains (&idem_id) { idem_id,
None nickname: params.nickname.clone(),
} }),
else { ])
recent_idem_ids.insert (0, idem_id); }
recent_idem_ids.truncate (30); }
Some (vec! [ _ => continue,
Message::Response1 (params.our_mac), };
Message::Response2 (message::Response2 {
idem_id, if let Some(resp) = resp {
nickname: params.nickname.clone (), socket
}), .send_to(&Message::many_to_vec(&resp)?, remote_addr)
]) .await?;
} }
}, }
_ => continue,
};
if let Some (resp) = resp {
socket.send_to (&Message::many_to_vec (&resp)?, remote_addr).await?;
}
}
} }

View File

@ -1,122 +1,117 @@
use thiserror::Error; use thiserror::Error;
type Result <T> = std::result::Result <T, TlvError>; type Result<T> = std::result::Result<T, TlvError>;
#[derive (Debug, Error)] #[derive(Debug, Error)]
pub enum TlvError { pub enum TlvError {
#[error ("Buffer too big")] #[error("Buffer too big")]
BufferTooBig, BufferTooBig,
// Violets are purple, // Violets are purple,
// To live is to suffer, // To live is to suffer,
// The data is too big, // The data is too big,
// For the gosh-darn buffer. // For the gosh-darn buffer.
#[error("Data too big")]
#[error ("Data too big")] DataTooBig,
DataTooBig, #[error(transparent)]
#[error (transparent)] Io(#[from] std::io::Error),
Io (#[from] std::io::Error), #[error("Actual bytes didn't match expected bytes")]
#[error ("Actual bytes didn't match expected bytes")] NotExpected,
NotExpected, #[error(transparent)]
#[error (transparent)] TryFromInt(#[from] std::num::TryFromIntError),
TryFromInt (#[from] std::num::TryFromIntError),
} }
pub struct Writer <W> { pub struct Writer<W> {
_x: std::marker::PhantomData <W>, _x: std::marker::PhantomData<W>,
} }
impl <W: std::io::Write> Writer <W> { impl<W: std::io::Write> Writer<W> {
fn length (w: &mut W, x: u32) -> Result <()> { fn length(w: &mut W, x: u32) -> Result<()> {
w.write_all (&x.to_le_bytes ())?; w.write_all(&x.to_le_bytes())?;
Ok (()) Ok(())
} }
pub fn lv_bytes (w: &mut W, b: &[u8]) -> Result <()> { pub fn lv_bytes(w: &mut W, b: &[u8]) -> Result<()> {
if b.len () > 2_000_000_000 { if b.len() > 2_000_000_000 {
return Err (TlvError::BufferTooBig); return Err(TlvError::BufferTooBig);
} }
let l = u32::try_from (b.len ())?; let l = u32::try_from(b.len())?;
Self::length (w, l)?; Self::length(w, l)?;
w.write_all (b)?; w.write_all(b)?;
Ok (()) Ok(())
} }
} }
pub struct Reader <R> { pub struct Reader<R> {
_x: std::marker::PhantomData <R>, _x: std::marker::PhantomData<R>,
} }
impl <R: std::io::Read> Reader <R> { impl<R: std::io::Read> Reader<R> {
pub fn expect (r: &mut R, expected: &[u8]) -> Result <()> { pub fn expect(r: &mut R, expected: &[u8]) -> Result<()> {
let mut actual = vec! [0u8; expected.len ()]; let mut actual = vec![0u8; expected.len()];
r.read_exact (&mut actual)?; r.read_exact(&mut actual)?;
if actual != expected { if actual != expected {
return Err (TlvError::NotExpected); return Err(TlvError::NotExpected);
} }
Ok (()) Ok(())
} }
pub fn length (r: &mut R) -> Result <u32> { pub fn length(r: &mut R) -> Result<u32> {
let mut buf = [0; 4]; let mut buf = [0; 4];
r.read_exact (&mut buf)?; r.read_exact(&mut buf)?;
Ok (u32::from_le_bytes (buf)) Ok(u32::from_le_bytes(buf))
} }
pub fn lv_bytes_to_vec (r: &mut R, limit: usize) -> Result <Vec <u8>> { pub fn lv_bytes_to_vec(r: &mut R, limit: usize) -> Result<Vec<u8>> {
let l = Self::length (r)?; let l = Self::length(r)?;
let l = usize::try_from (l)?; let l = usize::try_from(l)?;
if l > limit { if l > limit {
return Err (TlvError::DataTooBig); return Err(TlvError::DataTooBig);
} }
let mut v = vec! [0u8; l]; let mut v = vec![0u8; l];
r.read_exact (&mut v)?; r.read_exact(&mut v)?;
Ok (v) Ok(v)
} }
pub fn u8 (r: &mut R) -> std::io::Result <u8> { pub fn u8(r: &mut R) -> std::io::Result<u8> {
let mut buf = [0]; let mut buf = [0];
r.read_exact (&mut buf)?; r.read_exact(&mut buf)?;
Ok (buf [0]) Ok(buf[0])
} }
} }
#[cfg (test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[test] #[test]
fn test_1 () -> Result <()> { fn test_1() -> Result<()> {
use std::io::Cursor; use std::io::Cursor;
let b = "hi there".as_bytes (); let b = "hi there".as_bytes();
let mut w = Cursor::new (Vec::default ()); let mut w = Cursor::new(Vec::default());
super::Writer::lv_bytes (&mut w, b)?; super::Writer::lv_bytes(&mut w, b)?;
let v = w.into_inner (); let v = w.into_inner();
assert_eq! (v, vec! [ assert_eq!(v, vec![8, 0, 0, 0, 104, 105, 32, 116, 104, 101, 114, 101,]);
8, 0, 0, 0,
104, 105, 32, let mut r = Cursor::new(v);
116, 104, 101, 114, 101,
]); let buf = Reader::lv_bytes_to_vec(&mut r, 1024)?;
let mut r = Cursor::new (v); assert_eq!(buf.len(), b.len());
assert_eq!(b, &buf);
let buf = Reader::lv_bytes_to_vec (&mut r, 1024)?;
Ok(())
assert_eq! (buf.len (), b.len ()); }
assert_eq! (b, &buf);
Ok (())
}
} }