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

View File

@ -1,40 +1,26 @@
type Mac = [u8; 6];
pub fn debug () {
for input in [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 1],
] {
assert_eq! (unmix (mix (input)), input);
}
println! ("Passed");
pub fn debug() {
for input in [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 1],
] {
assert_eq!(unmix(mix(input)), input);
}
println!("Passed");
}
// 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
// numbers differ.
fn mix (i: Mac) -> Mac {
[
i [0] ^ i [5],
i [1] ^ i [4],
i [2] ^ i [3],
i [3],
i [4],
i [5],
]
fn mix(i: Mac) -> Mac {
[i[0] ^ i[5], i[1] ^ i[4], i[2] ^ i[3], i[3], i[4], i[5]]
}
fn unmix (i: Mac) -> Mac {
[
i [0] ^ i [5],
i [1] ^ i [4],
i [2] ^ i [3],
i [3],
i [4],
i [5],
]
fn unmix(i: Mac) -> Mac {
[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::*;
struct ServerResponse {
mac: Option <[u8; 6]>,
nickname: Option <String>,
mac: Option<[u8; 6]>,
nickname: Option<String>,
}
struct ConfigFile {
nicknames: HashMap <String, String>,
nicknames: HashMap<String, String>,
}
struct ClientParams {
common: app_common::Params,
bind_addrs: Vec <Ipv4Addr>,
nicknames: HashMap <String, String>,
timeout_ms: u64,
common: app_common::Params,
bind_addrs: Vec<Ipv4Addr>,
nicknames: HashMap<String, String>,
timeout_ms: u64,
}
pub async fn client <I: Iterator <Item=String>> (args: I) -> Result <(), AppError> {
match get_mac_address() {
Ok(Some(ma)) => {
println!("Our MAC addr = {}", ma);
}
Ok(None) => println!("No MAC address found."),
Err(e) => println!("{:?}", e),
}
let params = configure_client (args)?;
let socket = make_socket (&params.common, params.bind_addrs).await?;
let msg = Message::new_request1 ().to_vec ()?;
tokio::spawn (send_requests (Arc::clone (&socket), params.common, msg));
let mut peers = HashMap::with_capacity (10);
timeout (Duration::from_millis (params.timeout_ms), listen_for_responses (&socket, params.nicknames, &mut peers)).await.ok ();
let mut peers: Vec <_> = peers.into_iter ().collect ();
peers.sort_by_key (|(_, v)| v.mac);
println! ("Found {} peers:", peers.len ());
for (ip, resp) in peers.into_iter () {
let mac = match resp.mac {
None => {
println! ("<Unknown> = {}", ip);
continue;
},
Some (x) => x,
};
let nickname = match resp.nickname {
None => {
println! ("{} = {}", MacAddress::new (mac), ip.ip ());
continue;
},
Some (x) => x,
};
println! ("{} = {} `{}`", MacAddress::new (mac), ip.ip (), nickname);
}
Ok (())
pub async fn client<I: Iterator<Item = String>>(args: I) -> Result<(), AppError> {
match get_mac_address() {
Ok(Some(ma)) => {
println!("Our MAC addr = {}", ma);
}
Ok(None) => println!("No MAC address found."),
Err(e) => println!("{:?}", e),
}
let params = configure_client(args)?;
let socket = make_socket(&params.common, params.bind_addrs).await?;
let msg = Message::new_request1().to_vec()?;
tokio::spawn(send_requests(Arc::clone(&socket), params.common, msg));
let mut peers = HashMap::with_capacity(10);
timeout(
Duration::from_millis(params.timeout_ms),
listen_for_responses(&socket, params.nicknames, &mut peers),
)
.await
.ok();
let mut peers: Vec<_> = peers.into_iter().collect();
peers.sort_by_key(|(_, v)| v.mac);
println!("Found {} peers:", peers.len());
for (ip, resp) in peers.into_iter() {
let mac = match resp.mac {
None => {
println!("<Unknown> = {}", ip);
continue;
}
Some(x) => x,
};
let nickname = match resp.nickname {
None => {
println!("{} = {}", MacAddress::new(mac), ip.ip());
continue;
}
Some(x) => x,
};
println!("{} = {} `{}`", MacAddress::new(mac), ip.ip(), nickname);
}
Ok(())
}
pub async fn find_nick <I: Iterator <Item=String>> (mut args: I) -> Result <(), AppError>
{
let mut nick = None;
let mut timeout_ms = 500;
let ConfigFile {
nicknames,
} = load_config_file ();
while let Some (arg) = args.next () {
match arg.as_str () {
"--timeout-ms" => {
timeout_ms = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => u64::from_str (&x)?,
};
},
_ => nick = Some (arg),
}
}
let needle_nick = nick.ok_or_else (|| CliArgError::MissingRequiredArg ("nickname".to_string ()))?;
let needle_nick = Some (needle_nick);
let common_params = Default::default ();
let socket = make_socket (&common_params, get_ips ()?).await?;
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 {
let (msgs, remote_addr) = match recv_msg_from (&socket).await {
Err (_) => continue,
Ok (x) => x,
};
let mut resp = ServerResponse {
mac: None,
nickname: None,
};
for msg in msgs.into_iter () {
match msg {
Message::Response1 (x) => resp.mac = x,
Message::Response2 (x) => resp.nickname = Some (x.nickname),
_ => (),
}
}
resp.nickname = get_peer_nickname (&nicknames, resp.mac, resp.nickname);
if resp.nickname == needle_nick {
println! ("{}", remote_addr.ip ());
return;
}
}}).await?;
Ok (())
pub async fn find_nick<I: Iterator<Item = String>>(mut args: I) -> Result<(), AppError> {
let mut nick = None;
let mut timeout_ms = 500;
let ConfigFile { nicknames } = load_config_file();
while let Some(arg) = args.next() {
match arg.as_str() {
"--timeout-ms" => {
timeout_ms = match args.next() {
None => return Err(CliArgError::MissingArgumentValue(arg).into()),
Some(x) => u64::from_str(&x)?,
};
}
_ => nick = Some(arg),
}
}
let needle_nick =
nick.ok_or_else(|| CliArgError::MissingRequiredArg("nickname".to_string()))?;
let needle_nick = Some(needle_nick);
let common_params = Default::default();
let socket = make_socket(&common_params, get_ips()?).await?;
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 {
let (msgs, remote_addr) = match recv_msg_from(&socket).await {
Err(_) => continue,
Ok(x) => x,
};
let mut resp = ServerResponse {
mac: None,
nickname: None,
};
for msg in msgs.into_iter() {
match msg {
Message::Response1(x) => resp.mac = x,
Message::Response2(x) => resp.nickname = Some(x.nickname),
_ => (),
}
}
resp.nickname = get_peer_nickname(&nicknames, resp.mac, resp.nickname);
if resp.nickname == needle_nick {
println!("{}", remote_addr.ip());
return;
}
}
})
.await?;
Ok(())
}
fn configure_client <I: Iterator <Item=String>> (mut args: I)
-> Result <ClientParams, AppError>
{
let mut bind_addrs = vec! [];
let mut timeout_ms = 500;
let ConfigFile {
nicknames,
} = load_config_file ();
while let Some (arg) = args.next () {
match arg.as_str () {
"--bind-addr" => {
bind_addrs.push (match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => Ipv4Addr::from_str (&x)?,
});
},
"--timeout-ms" => {
timeout_ms = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => u64::from_str (&x)?,
};
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
}
}
if bind_addrs.is_empty () {
bind_addrs = get_ips ()?;
}
Ok (ClientParams {
common: Default::default (),
bind_addrs,
nicknames,
timeout_ms,
})
fn configure_client<I: Iterator<Item = String>>(mut args: I) -> Result<ClientParams, AppError> {
let mut bind_addrs = vec![];
let mut timeout_ms = 500;
let ConfigFile { nicknames } = load_config_file();
while let Some(arg) = args.next() {
match arg.as_str() {
"--bind-addr" => {
bind_addrs.push(match args.next() {
None => return Err(CliArgError::MissingArgumentValue(arg).into()),
Some(x) => Ipv4Addr::from_str(&x)?,
});
}
"--timeout-ms" => {
timeout_ms = match args.next() {
None => return Err(CliArgError::MissingArgumentValue(arg).into()),
Some(x) => u64::from_str(&x)?,
};
}
_ => return Err(CliArgError::UnrecognizedArgument(arg).into()),
}
}
if bind_addrs.is_empty() {
bind_addrs = get_ips()?;
}
Ok(ClientParams {
common: Default::default(),
bind_addrs,
nicknames,
timeout_ms,
})
}
fn load_config_file () -> ConfigFile {
let mut nicknames: HashMap <String, String> = Default::default ();
if let Some (proj_dirs) = find_project_dirs () {
let mut ini = Ini::new_cs ();
let path = proj_dirs.config_local_dir ().join ("client.ini");
if ini.load (&path).is_ok () {
let map_ref = ini.get_map_ref ();
if let Some (x) = map_ref.get ("nicknames") {
for (k, v) in x {
if let Some (v) = v {
let k = k.replace ('-', ":");
nicknames.insert (k, v.to_string ());
}
}
}
}
}
ConfigFile {
nicknames,
}
fn load_config_file() -> ConfigFile {
let mut nicknames: HashMap<String, String> = Default::default();
if let Some(proj_dirs) = find_project_dirs() {
let mut ini = Ini::new_cs();
let path = proj_dirs.config_local_dir().join("client.ini");
if ini.load(&path).is_ok() {
let map_ref = ini.get_map_ref();
if let Some(x) = map_ref.get("nicknames") {
for (k, v) in x {
if let Some(v) = v {
let k = k.replace('-', ":");
nicknames.insert(k, v.to_string());
}
}
}
}
}
ConfigFile { nicknames }
}
async fn make_socket (
common_params: &app_common::Params,
bind_addrs: Vec <Ipv4Addr>,
) -> Result <Arc <UdpSocket>, AppError> {
let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, 0)).await?;
for bind_addr in &bind_addrs {
if let Err (e) = socket.join_multicast_v4 (common_params.multicast_addr, *bind_addr) {
println! ("Error joining multicast group with iface {}: {:?}", bind_addr, e);
}
}
Ok (Arc::new (socket))
async fn make_socket(
common_params: &app_common::Params,
bind_addrs: Vec<Ipv4Addr>,
) -> Result<Arc<UdpSocket>, AppError> {
let socket = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)).await?;
for bind_addr in &bind_addrs {
if let Err(e) = socket.join_multicast_v4(common_params.multicast_addr, *bind_addr) {
println!(
"Error joining multicast group with iface {}: {:?}",
bind_addr, e
);
}
}
Ok(Arc::new(socket))
}
async fn send_requests (
socket: Arc <UdpSocket>,
params: app_common::Params,
msg: Vec <u8>,
)
-> Result <(), AppError>
{
for _ in 0..10 {
socket.send_to (&msg, (params.multicast_addr, params.server_port)).await?;
sleep (Duration::from_millis (100)).await;
}
Ok::<_, AppError> (())
async fn send_requests(
socket: Arc<UdpSocket>,
params: app_common::Params,
msg: Vec<u8>,
) -> Result<(), AppError> {
for _ in 0..10 {
socket
.send_to(&msg, (params.multicast_addr, params.server_port))
.await?;
sleep(Duration::from_millis(100)).await;
}
Ok::<_, AppError>(())
}
async fn listen_for_responses (
socket: &UdpSocket,
nicknames: HashMap <String, String>,
peers: &mut HashMap <SocketAddr, ServerResponse>
async fn listen_for_responses(
socket: &UdpSocket,
nicknames: HashMap<String, String>,
peers: &mut HashMap<SocketAddr, ServerResponse>,
) {
loop {
let (msgs, remote_addr) = match recv_msg_from (socket).await {
Err (_) => continue,
Ok (x) => x,
};
let mut resp = ServerResponse {
mac: None,
nickname: None,
};
for msg in msgs.into_iter () {
match msg {
Message::Response1 (x) => resp.mac = x,
Message::Response2 (x) => resp.nickname = Some (x.nickname),
_ => (),
}
}
resp.nickname = get_peer_nickname (&nicknames, resp.mac, resp.nickname);
peers.insert (remote_addr, resp);
}
loop {
let (msgs, remote_addr) = match recv_msg_from(socket).await {
Err(_) => continue,
Ok(x) => x,
};
let mut resp = ServerResponse {
mac: None,
nickname: None,
};
for msg in msgs.into_iter() {
match msg {
Message::Response1(x) => resp.mac = x,
Message::Response2(x) => resp.nickname = Some(x.nickname),
_ => (),
}
}
resp.nickname = get_peer_nickname(&nicknames, resp.mac, resp.nickname);
peers.insert(remote_addr, resp);
}
}
fn get_peer_nickname (
nicknames: &HashMap <String, String>,
mac: Option <[u8; 6]>,
peer_nickname: Option <String>
) -> Option <String>
{
match peer_nickname.as_deref () {
None => (),
Some ("") => (),
_ => return peer_nickname,
}
if let Some (mac) = &mac {
return nicknames.get (&format! ("{}", MacAddress::new (*mac))).cloned ()
}
None
fn get_peer_nickname(
nicknames: &HashMap<String, String>,
mac: Option<[u8; 6]>,
peer_nickname: Option<String>,
) -> Option<String> {
match peer_nickname.as_deref() {
None => (),
Some("") => (),
_ => return peer_nickname,
}
if let Some(mac) = &mac {
return nicknames
.get(&format!("{}", MacAddress::new(*mac)))
.cloned();
}
None
}
#[cfg (test)]
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_nicknames () {
let mut nicks = HashMap::new ();
for (k, v) in [
("01:01:01:01:01:01", "phoenix")
] {
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
( 1, (None, None), None),
// If the server tells us its MAC, we can look up our nickname for it
( 2, (Some ([1, 1, 1, 1, 1, 1]), None), Some ("phoenix")),
// Unless it's not in our nick list.
( 3, (Some ([1, 1, 1, 1, 1, 2]), None), None),
// If the server tells us its nickname, that always takes priority
( 4, (None, Some ("snowflake")), Some ("snowflake")),
( 5, (Some ([1, 1, 1, 1, 1, 1]), Some ("snowflake")), Some ("snowflake")),
( 6, (Some ([1, 1, 1, 1, 1, 2]), 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")),
( 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);
}
}
use super::*;
#[test]
fn test_nicknames() {
let mut nicks = HashMap::new();
for (k, v) in [("01:01:01:01:01:01", "phoenix")] {
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
(1, (None, None), None),
// If the server tells us its MAC, we can look up our nickname for it
(2, (Some([1, 1, 1, 1, 1, 1]), None), Some("phoenix")),
// Unless it's not in our nick list.
(3, (Some([1, 1, 1, 1, 1, 2]), None), None),
// If the server tells us its nickname, that always takes priority
(4, (None, Some("snowflake")), Some("snowflake")),
(
5,
(Some([1, 1, 1, 1, 1, 1]), Some("snowflake")),
Some("snowflake"),
),
(
6,
(Some([1, 1, 1, 1, 1, 2]), 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")),
(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::{
net::Ipv4Addr,
process::Command,
str::FromStr,
};
use std::{net::Ipv4Addr, process::Command, str::FromStr};
#[derive (Debug, thiserror::Error)]
#[derive(Debug, thiserror::Error)]
pub enum IpError {
#[error (transparent)]
Io (#[from] std::io::Error),
#[error (transparent)]
FromUtf8 (#[from] std::string::FromUtf8Error),
#[error ("Self-IP detection is not implemented on Mac OS")]
NotImplementedOnMac,
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
FromUtf8(#[from] std::string::FromUtf8Error),
#[error("Self-IP detection is not implemented on Mac OS")]
NotImplementedOnMac,
}
#[cfg(target_os = "linux")]
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
let output = linux::get_ip_addr_output ()?;
Ok (linux::parse_ip_addr_output (&output))
pub fn get_ips() -> Result<Vec<Ipv4Addr>, IpError> {
let output = linux::get_ip_addr_output()?;
Ok(linux::parse_ip_addr_output(&output))
}
#[cfg(target_os = "macos")]
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
Err (IpError::NotImplementedOnMac)
pub fn get_ips() -> Result<Vec<Ipv4Addr>, IpError> {
Err(IpError::NotImplementedOnMac)
}
#[cfg(target_os = "windows")]
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
let output = windows::get_ip_config_output ()?;
Ok (windows::parse_ip_config_output (&output))
pub fn get_ips() -> Result<Vec<Ipv4Addr>, IpError> {
let output = windows::get_ip_config_output()?;
Ok(windows::parse_ip_config_output(&output))
}
#[cfg(target_os = "linux")]
pub mod linux {
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)
}
use super::*;
pub fn parse_ip_addr_output (output: &str) -> Vec <Ipv4Addr> {
// I wrote this in FP style because I was bored.
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 ()
}
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> {
// I wrote this in FP style because I was bored.
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")]
pub mod windows {
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)
}
use super::*;
pub fn parse_ip_config_output (output: &str) -> Vec <Ipv4Addr> {
let mut addrs = vec! [];
for line in output.lines () {
let line = line.trim_start ();
// 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
}
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)
}
#[cfg (test)]
mod test {
use super::*;
#[test]
fn test () {
for (input, expected) in [
(
r"
pub fn parse_ip_config_output(output: &str) -> Vec<Ipv4Addr> {
let mut addrs = vec![];
for line in output.lines() {
let line = line.trim_start();
// 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)]
mod test {
use super::*;
#[test]
fn test() {
for (input, expected) in [(
r"
IPv4 Address . . .. . . . : 192.168.1.1
",
vec! [
Ipv4Addr::new (192, 168, 1, 1),
]
),
] {
let actual = parse_ip_config_output (input);
assert_eq! (actual, expected);
}
}
}
vec![Ipv4Addr::new(192, 168, 1, 1)],
)] {
let actual = parse_ip_config_output(input);
assert_eq!(actual, expected);
}
}
}
}

View File

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

View File

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

View File

@ -1,148 +1,149 @@
use crate::prelude::*;
#[derive (Clone)]
#[derive(Clone)]
struct Params {
common: app_common::Params,
bind_addrs: Vec <Ipv4Addr>,
nickname: String,
our_mac: Option <[u8; 6]>,
common: app_common::Params,
bind_addrs: Vec<Ipv4Addr>,
nickname: String,
our_mac: Option<[u8; 6]>,
}
pub async fn server <I: Iterator <Item=String>> (args: I) -> Result <(), AppError>
{
match get_mac_address() {
Ok(Some(ma)) => {
println!("Our MAC addr = {}", ma);
}
Ok(None) => println!("No MAC address found."),
Err(e) => println!("{:?}", e),
}
let params = configure (args)?;
let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, params.common.server_port)).await?;
for bind_addr in &params.bind_addrs {
if let Err (e) = socket.join_multicast_v4 (params.common.multicast_addr, *bind_addr) {
println! ("Error joining multicast group with iface {}: {:?}", bind_addr, e);
}
}
serve_interface (params, socket).await?;
Ok (())
pub async fn server<I: Iterator<Item = String>>(args: I) -> Result<(), AppError> {
match get_mac_address() {
Ok(Some(ma)) => {
println!("Our MAC addr = {}", ma);
}
Ok(None) => println!("No MAC address found."),
Err(e) => println!("{:?}", e),
}
let params = configure(args)?;
let socket = UdpSocket::bind(SocketAddrV4::new(
Ipv4Addr::UNSPECIFIED,
params.common.server_port,
))
.await?;
for bind_addr in &params.bind_addrs {
if let Err(e) = socket.join_multicast_v4(params.common.multicast_addr, *bind_addr) {
println!(
"Error joining multicast group with iface {}: {:?}",
bind_addr, e
);
}
}
serve_interface(params, socket).await?;
Ok(())
}
fn configure <I: Iterator <Item=String>> (mut args: I) -> Result <Params, AppError>
{
let common = app_common::Params::default ();
let mut bind_addrs = vec![];
let mut nickname = String::new ();
if let Some (proj_dirs) = find_project_dirs () {
let mut ini = Ini::new_cs ();
let path = proj_dirs.config_local_dir ().join ("server.ini");
if ini.load (&path).is_ok () {
if let Some (x) = ini.get ("server", "nickname") {
nickname = x;
eprintln! ("Loaded nickname {:?}", nickname);
}
}
else {
eprintln! ("Can't load ini from {:?}, didn't load default configs", path);
}
}
else {
eprintln! ("Can't find config dir, didn't load default configs");
}
while let Some (arg) = args.next () {
match arg.as_str () {
"--bind-addr" => {
bind_addrs.push (match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => Ipv4Addr::from_str (&x)?,
});
},
"--nickname" => {
nickname = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => x
};
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
}
}
let our_mac = get_mac_address ()?.map (|x| x.bytes ());
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");
}
if bind_addrs.is_empty () {
println! ("No bind addresses given, auto-detecting all local IPs");
bind_addrs = get_ips ()?;
}
Ok (Params {
common,
bind_addrs,
nickname,
our_mac,
})
fn configure<I: Iterator<Item = String>>(mut args: I) -> Result<Params, AppError> {
let common = app_common::Params::default();
let mut bind_addrs = vec![];
let mut nickname = String::new();
if let Some(proj_dirs) = find_project_dirs() {
let mut ini = Ini::new_cs();
let path = proj_dirs.config_local_dir().join("server.ini");
if ini.load(&path).is_ok() {
if let Some(x) = ini.get("server", "nickname") {
nickname = x;
eprintln!("Loaded nickname {:?}", nickname);
}
} else {
eprintln!(
"Can't load ini from {:?}, didn't load default configs",
path
);
}
} else {
eprintln!("Can't find config dir, didn't load default configs");
}
while let Some(arg) = args.next() {
match arg.as_str() {
"--bind-addr" => {
bind_addrs.push(match args.next() {
None => return Err(CliArgError::MissingArgumentValue(arg).into()),
Some(x) => Ipv4Addr::from_str(&x)?,
});
}
"--nickname" => {
nickname = match args.next() {
None => return Err(CliArgError::MissingArgumentValue(arg).into()),
Some(x) => x,
};
}
_ => return Err(CliArgError::UnrecognizedArgument(arg).into()),
}
}
let our_mac = get_mac_address()?.map(|x| x.bytes());
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"
);
}
if bind_addrs.is_empty() {
println!("No bind addresses given, auto-detecting all local IPs");
bind_addrs = get_ips()?;
}
Ok(Params {
common,
bind_addrs,
nickname,
our_mac,
})
}
async fn serve_interface (
params: Params,
socket: UdpSocket,
)
-> Result <(), AppError>
{
let mut recent_idem_ids = Vec::with_capacity (32);
loop {
println! ("Listening...");
let (req_msgs, remote_addr) = match recv_msg_from (&socket).await {
Ok (x) => x,
Err (e) => {
println! ("Error while receiving message: {:?}", e);
continue;
},
};
let req = match req_msgs.into_iter ().next () {
Some (x) => x,
_ => {
println! ("Don't know how to handle this message, ignoring");
continue;
},
};
let resp = match req {
Message::Request1 {
mac: None,
idem_id,
} => {
if recent_idem_ids.contains (&idem_id) {
None
}
else {
recent_idem_ids.insert (0, idem_id);
recent_idem_ids.truncate (30);
Some (vec! [
Message::Response1 (params.our_mac),
Message::Response2 (message::Response2 {
idem_id,
nickname: params.nickname.clone (),
}),
])
}
},
_ => continue,
};
if let Some (resp) = resp {
socket.send_to (&Message::many_to_vec (&resp)?, remote_addr).await?;
}
}
async fn serve_interface(params: Params, socket: UdpSocket) -> Result<(), AppError> {
let mut recent_idem_ids = Vec::with_capacity(32);
loop {
println!("Listening...");
let (req_msgs, remote_addr) = match recv_msg_from(&socket).await {
Ok(x) => x,
Err(e) => {
println!("Error while receiving message: {:?}", e);
continue;
}
};
let req = match req_msgs.into_iter().next() {
Some(x) => x,
_ => {
println!("Don't know how to handle this message, ignoring");
continue;
}
};
let resp = match req {
Message::Request1 { mac: None, idem_id } => {
if recent_idem_ids.contains(&idem_id) {
None
} else {
recent_idem_ids.insert(0, idem_id);
recent_idem_ids.truncate(30);
Some(vec![
Message::Response1(params.our_mac),
Message::Response2(message::Response2 {
idem_id,
nickname: params.nickname.clone(),
}),
])
}
}
_ => 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;
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 {
#[error ("Buffer too big")]
BufferTooBig,
// Violets are purple,
// To live is to suffer,
// The data is too big,
// For the gosh-darn buffer.
#[error ("Data too big")]
DataTooBig,
#[error (transparent)]
Io (#[from] std::io::Error),
#[error ("Actual bytes didn't match expected bytes")]
NotExpected,
#[error (transparent)]
TryFromInt (#[from] std::num::TryFromIntError),
#[error("Buffer too big")]
BufferTooBig,
// Violets are purple,
// To live is to suffer,
// The data is too big,
// For the gosh-darn buffer.
#[error("Data too big")]
DataTooBig,
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Actual bytes didn't match expected bytes")]
NotExpected,
#[error(transparent)]
TryFromInt(#[from] std::num::TryFromIntError),
}
pub struct Writer <W> {
_x: std::marker::PhantomData <W>,
pub struct Writer<W> {
_x: std::marker::PhantomData<W>,
}
impl <W: std::io::Write> Writer <W> {
fn length (w: &mut W, x: u32) -> Result <()> {
w.write_all (&x.to_le_bytes ())?;
Ok (())
}
pub fn lv_bytes (w: &mut W, b: &[u8]) -> Result <()> {
if b.len () > 2_000_000_000 {
return Err (TlvError::BufferTooBig);
}
let l = u32::try_from (b.len ())?;
Self::length (w, l)?;
w.write_all (b)?;
Ok (())
}
impl<W: std::io::Write> Writer<W> {
fn length(w: &mut W, x: u32) -> Result<()> {
w.write_all(&x.to_le_bytes())?;
Ok(())
}
pub fn lv_bytes(w: &mut W, b: &[u8]) -> Result<()> {
if b.len() > 2_000_000_000 {
return Err(TlvError::BufferTooBig);
}
let l = u32::try_from(b.len())?;
Self::length(w, l)?;
w.write_all(b)?;
Ok(())
}
}
pub struct Reader <R> {
_x: std::marker::PhantomData <R>,
pub struct Reader<R> {
_x: std::marker::PhantomData<R>,
}
impl <R: std::io::Read> Reader <R> {
pub fn expect (r: &mut R, expected: &[u8]) -> Result <()> {
let mut actual = vec! [0u8; expected.len ()];
r.read_exact (&mut actual)?;
if actual != expected {
return Err (TlvError::NotExpected);
}
Ok (())
}
pub fn length (r: &mut R) -> Result <u32> {
let mut buf = [0; 4];
r.read_exact (&mut buf)?;
Ok (u32::from_le_bytes (buf))
}
pub fn lv_bytes_to_vec (r: &mut R, limit: usize) -> Result <Vec <u8>> {
let l = Self::length (r)?;
let l = usize::try_from (l)?;
if l > limit {
return Err (TlvError::DataTooBig);
}
let mut v = vec! [0u8; l];
r.read_exact (&mut v)?;
Ok (v)
}
pub fn u8 (r: &mut R) -> std::io::Result <u8> {
let mut buf = [0];
r.read_exact (&mut buf)?;
Ok (buf [0])
}
impl<R: std::io::Read> Reader<R> {
pub fn expect(r: &mut R, expected: &[u8]) -> Result<()> {
let mut actual = vec![0u8; expected.len()];
r.read_exact(&mut actual)?;
if actual != expected {
return Err(TlvError::NotExpected);
}
Ok(())
}
pub fn length(r: &mut R) -> Result<u32> {
let mut buf = [0; 4];
r.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
pub fn lv_bytes_to_vec(r: &mut R, limit: usize) -> Result<Vec<u8>> {
let l = Self::length(r)?;
let l = usize::try_from(l)?;
if l > limit {
return Err(TlvError::DataTooBig);
}
let mut v = vec![0u8; l];
r.read_exact(&mut v)?;
Ok(v)
}
pub fn u8(r: &mut R) -> std::io::Result<u8> {
let mut buf = [0];
r.read_exact(&mut buf)?;
Ok(buf[0])
}
}
#[cfg (test)]
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_1 () -> Result <()> {
use std::io::Cursor;
let b = "hi there".as_bytes ();
let mut w = Cursor::new (Vec::default ());
super::Writer::lv_bytes (&mut w, b)?;
let v = w.into_inner ();
assert_eq! (v, vec! [
8, 0, 0, 0,
104, 105, 32,
116, 104, 101, 114, 101,
]);
let mut r = Cursor::new (v);
let buf = Reader::lv_bytes_to_vec (&mut r, 1024)?;
assert_eq! (buf.len (), b.len ());
assert_eq! (b, &buf);
Ok (())
}
use super::*;
#[test]
fn test_1() -> Result<()> {
use std::io::Cursor;
let b = "hi there".as_bytes();
let mut w = Cursor::new(Vec::default());
super::Writer::lv_bytes(&mut w, b)?;
let v = w.into_inner();
assert_eq!(v, vec![8, 0, 0, 0, 104, 105, 32, 116, 104, 101, 114, 101,]);
let mut r = Cursor::new(v);
let buf = Reader::lv_bytes_to_vec(&mut r, 1024)?;
assert_eq!(buf.len(), b.len());
assert_eq!(b, &buf);
Ok(())
}
}