Compare commits
	
		
			No commits in common. "0bb702f312d65c83761a04b827f6fcd5e4ff7704" and "2b4695934e8bcdedd388b43ff766b0ffb9cf67d6" have entirely different histories. 
		
	
	
		
			0bb702f312
			...
			2b4695934e
		
	
		| 
						 | 
					@ -43,15 +43,6 @@ version = "0.2.109"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01"
 | 
					checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "log"
 | 
					 | 
				
			||||||
version = "0.4.14"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "cfg-if",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "lookaround"
 | 
					name = "lookaround"
 | 
				
			||||||
version = "0.1.4"
 | 
					version = "0.1.4"
 | 
				
			||||||
| 
						 | 
					@ -59,7 +50,6 @@ dependencies = [
 | 
				
			||||||
 "mac_address",
 | 
					 "mac_address",
 | 
				
			||||||
 "rand",
 | 
					 "rand",
 | 
				
			||||||
 "thiserror",
 | 
					 "thiserror",
 | 
				
			||||||
 "tokio",
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
| 
						 | 
					@ -81,28 +71,6 @@ dependencies = [
 | 
				
			||||||
 "autocfg",
 | 
					 "autocfg",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "mio"
 | 
					 | 
				
			||||||
version = "0.7.14"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "libc",
 | 
					 | 
				
			||||||
 "log",
 | 
					 | 
				
			||||||
 "miow",
 | 
					 | 
				
			||||||
 "ntapi",
 | 
					 | 
				
			||||||
 "winapi",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "miow"
 | 
					 | 
				
			||||||
version = "0.3.7"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "winapi",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "nix"
 | 
					name = "nix"
 | 
				
			||||||
version = "0.22.2"
 | 
					version = "0.22.2"
 | 
				
			||||||
| 
						 | 
					@ -116,21 +84,6 @@ dependencies = [
 | 
				
			||||||
 "memoffset",
 | 
					 "memoffset",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "ntapi"
 | 
					 | 
				
			||||||
version = "0.3.6"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "winapi",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "pin-project-lite"
 | 
					 | 
				
			||||||
version = "0.2.7"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ppv-lite86"
 | 
					name = "ppv-lite86"
 | 
				
			||||||
version = "0.2.15"
 | 
					version = "0.2.15"
 | 
				
			||||||
| 
						 | 
					@ -226,19 +179,6 @@ dependencies = [
 | 
				
			||||||
 "syn",
 | 
					 "syn",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "tokio"
 | 
					 | 
				
			||||||
version = "1.14.0"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "autocfg",
 | 
					 | 
				
			||||||
 "libc",
 | 
					 | 
				
			||||||
 "mio",
 | 
					 | 
				
			||||||
 "pin-project-lite",
 | 
					 | 
				
			||||||
 "winapi",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "unicode-xid"
 | 
					name = "unicode-xid"
 | 
				
			||||||
version = "0.2.2"
 | 
					version = "0.2.2"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,9 +15,9 @@ version = "0.1.4"
 | 
				
			||||||
mac_address = "1.1.2"
 | 
					mac_address = "1.1.2"
 | 
				
			||||||
rand = "0.8.4"
 | 
					rand = "0.8.4"
 | 
				
			||||||
thiserror = "1.0.30"
 | 
					thiserror = "1.0.30"
 | 
				
			||||||
tokio = { version = "1.14.0", features = ["fs", "net", "rt", "time"] }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[profile.release]
 | 
					[profile.release]
 | 
				
			||||||
codegen-units = 1
 | 
					codegen-units = 1
 | 
				
			||||||
lto = true
 | 
					lto = true
 | 
				
			||||||
opt-level = "z"
 | 
					opt-level = "z"
 | 
				
			||||||
 | 
					panic = "abort"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								ideas.md
								
								
								
								
							
							
						
						
									
										1
									
								
								ideas.md
								
								
								
								
							| 
						 | 
					@ -3,4 +3,3 @@ Cool ideas that can be done but probably won't be.
 | 
				
			||||||
- Command for shell substituting IPs into commands
 | 
					- Command for shell substituting IPs into commands
 | 
				
			||||||
- Arbitrary TCP forwarding of (stdin? stdout? TCP?)
 | 
					- Arbitrary TCP forwarding of (stdin? stdout? TCP?)
 | 
				
			||||||
- Netcat replacement "Just send a file" _including filename_
 | 
					- Netcat replacement "Just send a file" _including filename_
 | 
				
			||||||
- Public-key crypto for trusting peers on first use (Hard cause it requires mutable disk state)
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,3 +21,12 @@ and get their IPs, so I might have to find another dependency
 | 
				
			||||||
for that. I think on Linux I can get it from `/sys/class/net` but
 | 
					for that. I think on Linux I can get it from `/sys/class/net` but
 | 
				
			||||||
I can't remember the trick for that. I think last time I did this
 | 
					I can't remember the trick for that. I think last time I did this
 | 
				
			||||||
(for that work project) I just punted to Qt.
 | 
					(for that work project) I just punted to Qt.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 01FPDWYH3ZY52DFF6PNRSA63GB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Sending an invalid packet directly to the server's UDP port crashes the server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 01FPDY51GHC7NRFV7Z14Q1KV7N
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Remove all unwraps. Since I used abort-on-panic to make the binary smaller,
 | 
				
			||||||
 | 
					it's not giving any error message for panics
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,61 +0,0 @@
 | 
				
			||||||
use crate::prelude::*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive (Debug, thiserror::Error)]
 | 
					 | 
				
			||||||
pub enum AppError {
 | 
					 | 
				
			||||||
	#[error (transparent)]
 | 
					 | 
				
			||||||
	AddrParse (#[from] std::net::AddrParseError),
 | 
					 | 
				
			||||||
	#[error (transparent)]
 | 
					 | 
				
			||||||
	CliArgs (#[from] CliArgError),
 | 
					 | 
				
			||||||
	#[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)]
 | 
					 | 
				
			||||||
	Tlv (#[from] crate::tlv::TlvError),
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive (Debug, thiserror::Error)]
 | 
					 | 
				
			||||||
pub enum CliArgError {
 | 
					 | 
				
			||||||
	#[error ("Missing value for argument `{0}`")]
 | 
					 | 
				
			||||||
	MissingArgumentValue (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))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[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,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Default for Params {
 | 
					 | 
				
			||||||
	fn default () -> Self {
 | 
					 | 
				
			||||||
		Self {
 | 
					 | 
				
			||||||
			server_port: 9040,
 | 
					 | 
				
			||||||
			multicast_addr: Ipv4Addr::new (225, 100, 99, 98),
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,99 +0,0 @@
 | 
				
			||||||
use crate::prelude::*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct ServerResponse {
 | 
					 | 
				
			||||||
	mac: Option <[u8; 6]>,
 | 
					 | 
				
			||||||
	nickname: Option <String>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub async fn client <I : Iterator <Item=String>> (mut args: I) -> Result <(), AppError> {
 | 
					 | 
				
			||||||
	use rand::RngCore;
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	let common_params = app_common::Params::default ();
 | 
					 | 
				
			||||||
	let mut bind_addr = "0.0.0.0".to_string ();
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	while let Some (arg) = args.next () {
 | 
					 | 
				
			||||||
		match arg.as_str () {
 | 
					 | 
				
			||||||
			"--bind-addr" => {
 | 
					 | 
				
			||||||
				bind_addr = match args.next () {
 | 
					 | 
				
			||||||
					None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
 | 
					 | 
				
			||||||
					Some (x) => x
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	let socket = UdpSocket::bind (&format! ("{}:0", bind_addr)).await?;
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	socket.join_multicast_v4 (common_params.multicast_addr, Ipv4Addr::from_str (&bind_addr)?)?;
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	let mut idem_id = [0u8; 8];
 | 
					 | 
				
			||||||
	rand::thread_rng ().fill_bytes (&mut idem_id);
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	let msg = Message::Request1 {
 | 
					 | 
				
			||||||
		idem_id,
 | 
					 | 
				
			||||||
		mac: None,
 | 
					 | 
				
			||||||
	}.to_vec ()?;
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	for _ in 0..10 {
 | 
					 | 
				
			||||||
		socket.send_to (&msg, (common_params.multicast_addr, common_params.server_port)).await?;
 | 
					 | 
				
			||||||
		sleep (Duration::from_millis (100)).await;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	let mut peers = HashMap::with_capacity (10);
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	timeout (Duration::from_secs (2), listen_for_responses (&socket, &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 (())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async fn listen_for_responses (
 | 
					 | 
				
			||||||
	socket: &UdpSocket, 
 | 
					 | 
				
			||||||
	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),
 | 
					 | 
				
			||||||
				_ => (),
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		peers.insert (remote_addr, resp);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										144
									
								
								src/ip.rs
								
								
								
								
							
							
						
						
									
										144
									
								
								src/ip.rs
								
								
								
								
							| 
						 | 
					@ -4,119 +4,59 @@ use std::{
 | 
				
			||||||
	str::FromStr,
 | 
						str::FromStr,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive (Debug, thiserror::Error)]
 | 
					use crate::AppError;
 | 
				
			||||||
pub enum IpError {
 | 
					
 | 
				
			||||||
	#[error (transparent)]
 | 
					pub fn get_ip_addr_output () -> Result <String, AppError> {
 | 
				
			||||||
	Io (#[from] std::io::Error),
 | 
						let output = Command::new ("ip")
 | 
				
			||||||
	#[error (transparent)]
 | 
						.arg ("addr")
 | 
				
			||||||
	FromUtf8 (#[from] std::string::FromUtf8Error),
 | 
						.output ()?;
 | 
				
			||||||
	#[error ("Self-IP detection is not implemented on Mac OS")]
 | 
						let output = output.stdout.as_slice ();
 | 
				
			||||||
	NotImplementedOnMac,
 | 
						let output = String::from_utf8 (output.to_vec ())?;
 | 
				
			||||||
 | 
						Ok (output)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(target_os = "linux")]
 | 
					pub fn parse_ip_addr_output (output: &str) -> Vec <Ipv4Addr> {
 | 
				
			||||||
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
 | 
						// I wrote this in FP style because I was bored.
 | 
				
			||||||
	let output = linux::get_ip_addr_output ()?;
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	Ok (linux::parse_ip_addr_output (&output))
 | 
						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 ())
 | 
				
			||||||
 | 
						.collect ()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(target_os = "macos")]
 | 
					pub fn get_ip_config_output () -> Result <String, AppError> {
 | 
				
			||||||
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
 | 
						let output = Command::new ("ipconfig")
 | 
				
			||||||
	Err (IpError::NotImplementedOnMac)
 | 
						.output ()?;
 | 
				
			||||||
 | 
						let output = output.stdout.as_slice ();
 | 
				
			||||||
 | 
						let output = String::from_utf8 (output.to_vec ())?;
 | 
				
			||||||
 | 
						Ok (output)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(target_os = "windows")]
 | 
					pub fn parse_ip_config_output (output: &str) -> Vec <Ipv4Addr> {
 | 
				
			||||||
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
 | 
						let mut addrs = vec! [];
 | 
				
			||||||
	let output = windows::get_ip_config_output ()?;
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	Ok (windows::parse_ip_config_output (&output))
 | 
						for line in output.lines () {
 | 
				
			||||||
}
 | 
							let line = line.trim_start ();
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
#[cfg(target_os = "linux")]
 | 
							// Maybe only works on English locales?
 | 
				
			||||||
pub mod linux {
 | 
							if ! line.starts_with ("IPv4 Address") {
 | 
				
			||||||
	use super::*;
 | 
								continue;
 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	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)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	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);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							let colon_pos = match line.find (':') {
 | 
				
			||||||
 | 
								None => continue,
 | 
				
			||||||
 | 
								Some (x) => x,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							let line = &line [colon_pos + 2..];
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		addrs
 | 
							let addr = match Ipv4Addr::from_str (line) {
 | 
				
			||||||
 | 
								Err (_) => continue,
 | 
				
			||||||
 | 
								Ok (x) => x,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							addrs.push (addr);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	#[cfg (test)]
 | 
						addrs
 | 
				
			||||||
	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);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										295
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										295
									
								
								src/main.rs
								
								
								
								
							| 
						 | 
					@ -1,25 +1,81 @@
 | 
				
			||||||
use prelude::*;
 | 
					use std::{
 | 
				
			||||||
 | 
						collections::HashMap,
 | 
				
			||||||
 | 
						env,
 | 
				
			||||||
 | 
						net::{
 | 
				
			||||||
 | 
							Ipv4Addr,
 | 
				
			||||||
 | 
							SocketAddr,
 | 
				
			||||||
 | 
							SocketAddrV4,
 | 
				
			||||||
 | 
							UdpSocket,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						str::FromStr,
 | 
				
			||||||
 | 
						time::{Duration, Instant},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use mac_address::{
 | 
				
			||||||
 | 
						MacAddress,
 | 
				
			||||||
 | 
						get_mac_address,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod app_common;
 | 
					 | 
				
			||||||
mod client;
 | 
					 | 
				
			||||||
mod ip;
 | 
					mod ip;
 | 
				
			||||||
pub mod message;
 | 
					mod message;
 | 
				
			||||||
mod prelude;
 | 
					 | 
				
			||||||
mod server;
 | 
					 | 
				
			||||||
mod tlv;
 | 
					mod tlv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main () -> Result <(), AppError> {
 | 
					use message::{
 | 
				
			||||||
	let rt = tokio::runtime::Builder::new_current_thread ()
 | 
						PACKET_SIZE,
 | 
				
			||||||
	.enable_io ()
 | 
						Message,
 | 
				
			||||||
	.enable_time ()
 | 
					};
 | 
				
			||||||
	.build ()?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rt.block_on (async_main ())?;
 | 
					#[derive (Debug, Error)]
 | 
				
			||||||
	
 | 
					pub enum AppError {
 | 
				
			||||||
	Ok (())
 | 
						#[error (transparent)]
 | 
				
			||||||
 | 
						AddrParse (#[from] std::net::AddrParseError),
 | 
				
			||||||
 | 
						#[error (transparent)]
 | 
				
			||||||
 | 
						CliArgs (#[from] CliArgError),
 | 
				
			||||||
 | 
						#[error (transparent)]
 | 
				
			||||||
 | 
						FromUtf8 (#[from] std::string::FromUtf8Error),
 | 
				
			||||||
 | 
						#[error (transparent)]
 | 
				
			||||||
 | 
						Io (#[from] std::io::Error),
 | 
				
			||||||
 | 
						#[error (transparent)]
 | 
				
			||||||
 | 
						MacAddr (#[from] mac_address::MacAddressError),
 | 
				
			||||||
 | 
						#[error (transparent)]
 | 
				
			||||||
 | 
						Message (#[from] message::MessageError),
 | 
				
			||||||
 | 
						#[error (transparent)]
 | 
				
			||||||
 | 
						Tlv (#[from] tlv::TlvError),
 | 
				
			||||||
 | 
						#[error (transparent)]
 | 
				
			||||||
 | 
						Utf8 (#[from] std::str::Utf8Error),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn async_main () -> Result <(), AppError> {
 | 
					#[derive (Debug, Error)]
 | 
				
			||||||
 | 
					pub enum CliArgError {
 | 
				
			||||||
 | 
						#[error ("Missing value for argument `{0}`")]
 | 
				
			||||||
 | 
						MissingArgumentValue (String),
 | 
				
			||||||
 | 
						#[error ("First argument should be a subcommand")]
 | 
				
			||||||
 | 
						MissingSubcommand,
 | 
				
			||||||
 | 
						#[error ("Unknown subcommand `{0}`")]
 | 
				
			||||||
 | 
						UnknownSubcommand (String),
 | 
				
			||||||
 | 
						#[error ("Unrecognized argument `{0}`")]
 | 
				
			||||||
 | 
						UnrecognizedArgument (String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct CommonParams {
 | 
				
			||||||
 | 
						// Servers bind on this port, clients must send to the port
 | 
				
			||||||
 | 
						server_port: u16,
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Clients and servers will all join the same multicast addr
 | 
				
			||||||
 | 
						multicast_addr: Ipv4Addr,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for CommonParams {
 | 
				
			||||||
 | 
						fn default () -> Self {
 | 
				
			||||||
 | 
							Self {
 | 
				
			||||||
 | 
								server_port: 9040,
 | 
				
			||||||
 | 
								multicast_addr: Ipv4Addr::new (225, 100, 99, 98),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main () -> Result <(), AppError> {
 | 
				
			||||||
	let mut args = env::args ();
 | 
						let mut args = env::args ();
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	let _exe_name = args.next ();
 | 
						let _exe_name = args.next ();
 | 
				
			||||||
| 
						 | 
					@ -36,20 +92,223 @@ async fn async_main () -> Result <(), AppError> {
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	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 ("client") => client::client (args).await?,
 | 
							Some ("client") => client (args)?,
 | 
				
			||||||
		Some ("my-ips") => my_ips ()?,
 | 
							Some ("my-ips") => my_ips ()?,
 | 
				
			||||||
		Some ("server") => server::server (args).await?,
 | 
							Some ("server") => server (args)?,
 | 
				
			||||||
		Some (x) => return Err (CliArgError::UnknownSubcommand (x.to_string ()).into ()),
 | 
							Some (x) => return Err (CliArgError::UnknownSubcommand (x.to_string ()).into ()),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	Ok (())
 | 
						Ok (())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(target_os = "linux")]
 | 
				
			||||||
fn my_ips () -> Result <(), AppError> {
 | 
					fn my_ips () -> Result <(), AppError> {
 | 
				
			||||||
	for addr in ip::get_ips ()?
 | 
						let output = ip::get_ip_addr_output ()?;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						for addr in ip::parse_ip_addr_output (&output)
 | 
				
			||||||
 | 
						.iter ()
 | 
				
			||||||
 | 
						.filter (|a| ! a.is_loopback ())
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		println! ("{:?}", addr);
 | 
							println! ("{:?}", addr);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	Ok (())
 | 
						Ok (())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(target_os = "macos")]
 | 
				
			||||||
 | 
					fn my_ips () -> Result <(), AppError> {
 | 
				
			||||||
 | 
						println! ("my-ips subcommand not implemented for macos");
 | 
				
			||||||
 | 
						Ok (())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(target_os = "windows")]
 | 
				
			||||||
 | 
					fn my_ips () -> Result <(), AppError> {
 | 
				
			||||||
 | 
						let output = ip::get_ip_config_output ()?;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						for addr in ip::parse_ip_config_output (&output) {
 | 
				
			||||||
 | 
							println! ("{:?}", addr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						Ok (())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ServerResponse {
 | 
				
			||||||
 | 
						mac: Option <[u8; 6]>,
 | 
				
			||||||
 | 
						nickname: Option <String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn client <I : Iterator <Item=String>> (mut args: I) -> Result <(), AppError> {
 | 
				
			||||||
 | 
						use rand::RngCore;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let mut common_params = CommonParams::default ();
 | 
				
			||||||
 | 
						let mut bind_addr = "0.0.0.0".to_string ();
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						while let Some (arg) = args.next () {
 | 
				
			||||||
 | 
							match arg.as_str () {
 | 
				
			||||||
 | 
								"--bind-addr" => {
 | 
				
			||||||
 | 
									bind_addr = match args.next () {
 | 
				
			||||||
 | 
										None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
 | 
				
			||||||
 | 
										Some (x) => x
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let socket = UdpSocket::bind (&format! ("{}:0", bind_addr))?;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						socket.join_multicast_v4 (&common_params.multicast_addr, &Ipv4Addr::from_str (&bind_addr)?)?;
 | 
				
			||||||
 | 
						socket.set_read_timeout (Some (Duration::from_millis (1_000)))?;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let mut idem_id = [0u8; 8];
 | 
				
			||||||
 | 
						rand::thread_rng ().fill_bytes (&mut idem_id);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let msg = Message::Request1 {
 | 
				
			||||||
 | 
							idem_id,
 | 
				
			||||||
 | 
							mac: None,
 | 
				
			||||||
 | 
						}.to_vec ()?;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						for _ in 0..10 {
 | 
				
			||||||
 | 
							socket.send_to (&msg, (common_params.multicast_addr, common_params.server_port))?;
 | 
				
			||||||
 | 
							std::thread::sleep (Duration::from_millis (100));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let start_time = Instant::now ();
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let mut peers = HashMap::with_capacity (10);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						while Instant::now () < start_time + Duration::from_secs (2) {
 | 
				
			||||||
 | 
							let (msgs, remote_addr) = match recv_msg_from (&socket) {
 | 
				
			||||||
 | 
								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),
 | 
				
			||||||
 | 
									_ => (),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							peers.insert (remote_addr, resp);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let mut peers: Vec <_> = peers.into_iter ().collect ();
 | 
				
			||||||
 | 
						peers.sort_by_key (|(k, 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 (())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn server <I: Iterator <Item=String>> (mut args: I) -> Result <(), AppError> 
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						let mut common_params = CommonParams::default ();
 | 
				
			||||||
 | 
						let mut bind_addr = "0.0.0.0".to_string ();
 | 
				
			||||||
 | 
						let mut nickname = String::new ();
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						while let Some (arg) = args.next () {
 | 
				
			||||||
 | 
							match arg.as_str () {
 | 
				
			||||||
 | 
								"--bind-addr" => {
 | 
				
			||||||
 | 
									bind_addr = match args.next () {
 | 
				
			||||||
 | 
										None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
 | 
				
			||||||
 | 
										Some (x) => 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");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::from_str (&bind_addr)?, common_params.server_port)).unwrap ();
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						socket.join_multicast_v4 (&common_params.multicast_addr, &([0u8, 0, 0, 0].into ())).unwrap ();
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						let mut recent_idem_ids = Vec::with_capacity (32);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						loop {
 | 
				
			||||||
 | 
							println! ("Waiting for messages...");
 | 
				
			||||||
 | 
							let (req_msgs, remote_addr) = recv_msg_from (&socket)?;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							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) {
 | 
				
			||||||
 | 
										println! ("Ignoring request we already processed");
 | 
				
			||||||
 | 
										None
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									else {
 | 
				
			||||||
 | 
										recent_idem_ids.insert (0, idem_id);
 | 
				
			||||||
 | 
										recent_idem_ids.truncate (30);
 | 
				
			||||||
 | 
										Some (vec! [
 | 
				
			||||||
 | 
											Message::Response1 (our_mac),
 | 
				
			||||||
 | 
											Message::Response2 (message::Response2 {
 | 
				
			||||||
 | 
												idem_id,
 | 
				
			||||||
 | 
												nickname: nickname.clone (),
 | 
				
			||||||
 | 
											}),
 | 
				
			||||||
 | 
										])
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								_ => continue,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if let Some (resp) = resp {
 | 
				
			||||||
 | 
								socket.send_to (&Message::many_to_vec (&resp)?, remote_addr).unwrap ();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)?;
 | 
				
			||||||
 | 
						buf.truncate (bytes_recved);
 | 
				
			||||||
 | 
						let msgs = Message::from_slice2 (&buf)?;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						Ok ((msgs, remote_addr))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										154
									
								
								src/message.rs
								
								
								
								
							
							
						
						
									
										154
									
								
								src/message.rs
								
								
								
								
							| 
						 | 
					@ -19,10 +19,10 @@ pub enum Message {
 | 
				
			||||||
	// 1
 | 
						// 1
 | 
				
			||||||
	Request1 {
 | 
						Request1 {
 | 
				
			||||||
		idem_id: [u8; 8],
 | 
							idem_id: [u8; 8],
 | 
				
			||||||
		mac: Option <Mac>
 | 
							mac: Option <[u8; 6]>
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	// 2
 | 
						// 2
 | 
				
			||||||
	Response1 (Option <Mac>),
 | 
						Response1 (Option <[u8; 6]>),
 | 
				
			||||||
	// 3
 | 
						// 3
 | 
				
			||||||
	Response2 (Response2),
 | 
						Response2 (Response2),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -66,7 +66,7 @@ impl Write for DummyWriter {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 <(), std::io::Error> 
 | 
				
			||||||
	where Cursor <T>: Write
 | 
						where Cursor <T>: Write
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		match self {
 | 
							match self {
 | 
				
			||||||
| 
						 | 
					@ -92,7 +92,7 @@ impl Message {
 | 
				
			||||||
				Self::write_response_2 (&mut dummy_writer, x)?;
 | 
									Self::write_response_2 (&mut dummy_writer, x)?;
 | 
				
			||||||
				
 | 
									
 | 
				
			||||||
				// Write length and real params to real output
 | 
									// Write length and real params to real output
 | 
				
			||||||
				let len = u32::try_from (dummy_writer.position)?;
 | 
									let len = u32::try_from (dummy_writer.position).unwrap ();
 | 
				
			||||||
				w.write_all (&len.to_le_bytes ())?;
 | 
									w.write_all (&len.to_le_bytes ())?;
 | 
				
			||||||
				Self::write_response_2 (w, x)?;
 | 
									Self::write_response_2 (w, x)?;
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
| 
						 | 
					@ -102,11 +102,13 @@ impl Message {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	fn write_response_2 <W: Write> (w: &mut W, params: &Response2) 
 | 
						fn write_response_2 <W: Write> (w: &mut W, params: &Response2) 
 | 
				
			||||||
	-> Result <(), MessageError>
 | 
						-> Result <(), std::io::Error>
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		w.write_all (¶ms.idem_id)?;
 | 
							w.write_all (¶ms.idem_id)?;
 | 
				
			||||||
		let nickname = params.nickname.as_bytes ();
 | 
							let nickname = params.nickname.as_bytes ();
 | 
				
			||||||
		tlv::Writer::<_>::lv_bytes (w, nickname)?;
 | 
							let nickname_len = u32::try_from (nickname.len ()).unwrap ();
 | 
				
			||||||
 | 
							w.write_all (&nickname_len.to_le_bytes ())?;
 | 
				
			||||||
 | 
							w.write_all (&nickname)?;
 | 
				
			||||||
		Ok (())
 | 
							Ok (())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
| 
						 | 
					@ -122,14 +124,14 @@ impl Message {
 | 
				
			||||||
		Ok (())
 | 
							Ok (())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn to_vec (&self) -> Result <Vec <u8>, MessageError> {
 | 
						pub fn to_vec (&self) -> Result <Vec <u8>, tlv::TlvError> {
 | 
				
			||||||
		let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE));
 | 
							let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE));
 | 
				
			||||||
		cursor.write_all (&MAGIC_NUMBER)?;
 | 
							cursor.write_all (&MAGIC_NUMBER)?;
 | 
				
			||||||
		self.write (&mut cursor)?;
 | 
							self.write (&mut cursor)?;
 | 
				
			||||||
		Ok (cursor.into_inner ())
 | 
							Ok (cursor.into_inner ())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn many_to_vec (msgs: &[Self]) -> Result <Vec <u8>, MessageError> {
 | 
						pub fn many_to_vec (msgs: &[Self]) -> Result <Vec <u8>, tlv::TlvError> {
 | 
				
			||||||
		let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE));
 | 
							let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE));
 | 
				
			||||||
		cursor.write_all (&MAGIC_NUMBER)?;
 | 
							cursor.write_all (&MAGIC_NUMBER)?;
 | 
				
			||||||
		for msg in msgs {
 | 
							for msg in msgs {
 | 
				
			||||||
| 
						 | 
					@ -138,6 +140,28 @@ impl Message {
 | 
				
			||||||
		Ok (cursor.into_inner ())
 | 
							Ok (cursor.into_inner ())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						fn read1 <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)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								_ => return Err (MessageError::UnknownType),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	fn read2 <R: std::io::Read> (r: &mut R) -> Result <Self, MessageError> {
 | 
						fn read2 <R: std::io::Read> (r: &mut R) -> Result <Self, MessageError> {
 | 
				
			||||||
		let t = tlv::Reader::u8 (r)?;
 | 
							let t = tlv::Reader::u8 (r)?;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
| 
						 | 
					@ -157,12 +181,24 @@ impl Message {
 | 
				
			||||||
				Self::Response1 (mac)
 | 
									Self::Response1 (mac)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			3 => {
 | 
								3 => {
 | 
				
			||||||
				tlv::Reader::<_>::length (r)?;
 | 
									let mut len = [0; 4];
 | 
				
			||||||
 | 
									r.read_exact (&mut len)?;
 | 
				
			||||||
 | 
									let _len = len;
 | 
				
			||||||
				
 | 
									
 | 
				
			||||||
				let mut idem_id = [0; 8];
 | 
									let mut idem_id = [0; 8];
 | 
				
			||||||
				r.read_exact (&mut idem_id)?;
 | 
									r.read_exact (&mut idem_id)?;
 | 
				
			||||||
				
 | 
									
 | 
				
			||||||
				let nickname = tlv::Reader::<_>::lv_bytes_to_vec (r, 64)?;
 | 
									let mut nickname_len = [0; 4];
 | 
				
			||||||
 | 
									r.read_exact (&mut nickname_len)?;
 | 
				
			||||||
 | 
									let nickname_len = u32::from_le_bytes (nickname_len);
 | 
				
			||||||
 | 
									let nickname_len = usize::try_from (nickname_len)?;
 | 
				
			||||||
 | 
									
 | 
				
			||||||
 | 
									if nickname_len > 64 {
 | 
				
			||||||
 | 
										return Err (MessageError::LengthPrefixTooLong ((64, nickname_len)));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									
 | 
				
			||||||
 | 
									let mut nickname = vec! [0u8; nickname_len];
 | 
				
			||||||
 | 
									r.read_exact (&mut nickname)?;
 | 
				
			||||||
				let nickname = String::from_utf8 (nickname)?;
 | 
									let nickname = String::from_utf8 (nickname)?;
 | 
				
			||||||
				
 | 
									
 | 
				
			||||||
				Self::Response2 (Response2 {
 | 
									Self::Response2 (Response2 {
 | 
				
			||||||
| 
						 | 
					@ -187,6 +223,12 @@ impl Message {
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						pub fn from_slice1 (buf: &[u8]) -> Result <Self, MessageError> {
 | 
				
			||||||
 | 
							let mut cursor = Cursor::new (buf);
 | 
				
			||||||
 | 
							tlv::Reader::expect (&mut cursor, &MAGIC_NUMBER)?;
 | 
				
			||||||
 | 
							Self::read1 (&mut cursor)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	pub fn from_slice2 (buf: &[u8]) -> Result <Vec <Self>, MessageError> {
 | 
						pub fn from_slice2 (buf: &[u8]) -> Result <Vec <Self>, MessageError> {
 | 
				
			||||||
		let mut cursor = Cursor::new (buf);
 | 
							let mut cursor = Cursor::new (buf);
 | 
				
			||||||
		tlv::Reader::expect (&mut cursor, &MAGIC_NUMBER)?;
 | 
							tlv::Reader::expect (&mut cursor, &MAGIC_NUMBER)?;
 | 
				
			||||||
| 
						 | 
					@ -206,7 +248,7 @@ mod test {
 | 
				
			||||||
	use super::*;
 | 
						use super::*;
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	#[test]
 | 
						#[test]
 | 
				
			||||||
	fn test_write_2 () -> Result <(), MessageError> {
 | 
						fn test_write_2 () {
 | 
				
			||||||
		for (input, expected) in [
 | 
							for (input, expected) in [
 | 
				
			||||||
			(
 | 
								(
 | 
				
			||||||
				vec! [
 | 
									vec! [
 | 
				
			||||||
| 
						 | 
					@ -254,15 +296,13 @@ mod test {
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			),
 | 
								),
 | 
				
			||||||
		] { 
 | 
							] { 
 | 
				
			||||||
			let actual = Message::many_to_vec (&input)?;
 | 
								let actual = Message::many_to_vec (&input).unwrap ();
 | 
				
			||||||
			assert_eq! (actual, expected, "{:?}", input);
 | 
								assert_eq! (actual, expected, "{:?}", input);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		Ok (())
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	#[test]
 | 
						#[test]
 | 
				
			||||||
	fn test_write_1 () -> Result <(), MessageError> {
 | 
						fn test_write_1 () {
 | 
				
			||||||
		for (input, expected) in [
 | 
							for (input, expected) in [
 | 
				
			||||||
			(
 | 
								(
 | 
				
			||||||
				Message::Request1 {
 | 
									Message::Request1 {
 | 
				
			||||||
| 
						 | 
					@ -304,15 +344,13 @@ mod test {
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			),
 | 
								),
 | 
				
			||||||
		].into_iter () {
 | 
							].into_iter () {
 | 
				
			||||||
			let actual = input.to_vec ()?;
 | 
								let actual = input.to_vec ().unwrap ();
 | 
				
			||||||
			assert_eq! (actual, expected, "{:?}", input);
 | 
								assert_eq! (actual, expected, "{:?}", input);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		Ok (())
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	#[test]
 | 
						#[test]
 | 
				
			||||||
	fn test_read_2 () -> Result <(), MessageError> {
 | 
						fn test_read_2 () {
 | 
				
			||||||
		for input in [
 | 
							for input in [
 | 
				
			||||||
			vec! [
 | 
								vec! [
 | 
				
			||||||
				Message::Request1 {
 | 
									Message::Request1 {
 | 
				
			||||||
| 
						 | 
					@ -334,11 +372,83 @@ mod test {
 | 
				
			||||||
				}),
 | 
									}),
 | 
				
			||||||
			],
 | 
								],
 | 
				
			||||||
		].into_iter () {
 | 
							].into_iter () {
 | 
				
			||||||
			let encoded = Message::many_to_vec (&input)?;
 | 
								let encoded = Message::many_to_vec (&input).unwrap ();
 | 
				
			||||||
			let decoded = Message::from_slice2 (&encoded)?;
 | 
								let decoded = Message::from_slice2 (&encoded).unwrap ();
 | 
				
			||||||
 | 
								assert_eq! (input, decoded);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						#[test]
 | 
				
			||||||
 | 
						fn test_read_1 () {
 | 
				
			||||||
 | 
							for input in [
 | 
				
			||||||
 | 
								Message::Request1 {
 | 
				
			||||||
 | 
									idem_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
 | 
				
			||||||
 | 
									mac: None,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Message::Response1 (Some ([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])),
 | 
				
			||||||
 | 
								Message::Response1 (None),
 | 
				
			||||||
 | 
							].into_iter () {
 | 
				
			||||||
 | 
								let encoded = input.to_vec ().unwrap ();
 | 
				
			||||||
 | 
								let decoded = Message::from_slice1 (&encoded).unwrap ();
 | 
				
			||||||
			assert_eq! (input, decoded);
 | 
								assert_eq! (input, decoded);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		Ok (())
 | 
							for (expected, input) 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,
 | 
				
			||||||
 | 
									],
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
								(
 | 
				
			||||||
 | 
									Message::Response1 (None), 
 | 
				
			||||||
 | 
									vec! [
 | 
				
			||||||
 | 
										// Magic number for LookAround packets
 | 
				
			||||||
 | 
										154, 74, 67, 129,
 | 
				
			||||||
 | 
										// Response tag
 | 
				
			||||||
 | 
										2, 
 | 
				
			||||||
 | 
										// MAC is None
 | 
				
			||||||
 | 
										0,
 | 
				
			||||||
 | 
										// New tag that older versions will just ignore
 | 
				
			||||||
 | 
										255,
 | 
				
			||||||
 | 
									],
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							].into_iter () {
 | 
				
			||||||
 | 
								let actual = Message::from_slice1 (&input).unwrap ();
 | 
				
			||||||
 | 
								assert_eq! (actual, expected, "{:?}", actual);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,38 +0,0 @@
 | 
				
			||||||
pub use std::{
 | 
					 | 
				
			||||||
	collections::HashMap,
 | 
					 | 
				
			||||||
	env,
 | 
					 | 
				
			||||||
	net::{
 | 
					 | 
				
			||||||
		Ipv4Addr,
 | 
					 | 
				
			||||||
		SocketAddr,
 | 
					 | 
				
			||||||
		SocketAddrV4,
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	str::FromStr,
 | 
					 | 
				
			||||||
	time::Duration,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub use mac_address::{
 | 
					 | 
				
			||||||
	MacAddress,
 | 
					 | 
				
			||||||
	get_mac_address,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
pub use tokio::{
 | 
					 | 
				
			||||||
	net::UdpSocket,
 | 
					 | 
				
			||||||
	time::{
 | 
					 | 
				
			||||||
		sleep,
 | 
					 | 
				
			||||||
		timeout,
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub use crate::{
 | 
					 | 
				
			||||||
	app_common::{
 | 
					 | 
				
			||||||
		self,
 | 
					 | 
				
			||||||
		AppError,
 | 
					 | 
				
			||||||
		CliArgError,
 | 
					 | 
				
			||||||
		recv_msg_from,
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	ip::get_ips,
 | 
					 | 
				
			||||||
	message::{
 | 
					 | 
				
			||||||
		self,
 | 
					 | 
				
			||||||
		PACKET_SIZE,
 | 
					 | 
				
			||||||
		Message,
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										133
									
								
								src/server.rs
								
								
								
								
							
							
						
						
									
										133
									
								
								src/server.rs
								
								
								
								
							| 
						 | 
					@ -1,133 +0,0 @@
 | 
				
			||||||
use crate::prelude::*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive (Clone)]
 | 
					 | 
				
			||||||
struct Params {
 | 
					 | 
				
			||||||
	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> 
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	let params = configure (args)?;
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	// This was too hard to do in a functional style
 | 
					 | 
				
			||||||
	let mut tasks = vec! [];
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	for bind_addr in ¶ms.bind_addrs {
 | 
					 | 
				
			||||||
		let socket = match UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, params.common.server_port)).await {
 | 
					 | 
				
			||||||
			Err (e) => {
 | 
					 | 
				
			||||||
				println! ("Error binding socket: {:?}", e);
 | 
					 | 
				
			||||||
				continue;
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			Ok (x) => x,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		socket.join_multicast_v4 (params.common.multicast_addr, *bind_addr)?;
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		dbg! (bind_addr);
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		tasks.push (tokio::spawn (serve_interface (params.clone (), socket)));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	for task in tasks {
 | 
					 | 
				
			||||||
		task.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 ();
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	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 found, 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?;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										38
									
								
								src/tlv.rs
								
								
								
								
							
							
						
						
									
										38
									
								
								src/tlv.rs
								
								
								
								
							| 
						 | 
					@ -6,14 +6,8 @@ type Result <T> = std::result::Result <T, TlvError>;
 | 
				
			||||||
pub enum TlvError {
 | 
					pub enum TlvError {
 | 
				
			||||||
	#[error ("Buffer too big")]
 | 
						#[error ("Buffer too big")]
 | 
				
			||||||
	BufferTooBig,
 | 
						BufferTooBig,
 | 
				
			||||||
	
 | 
						#[error ("Caller-provided buffer too small")]
 | 
				
			||||||
	// Violets are purple,
 | 
						CallerBufferTooSmall,
 | 
				
			||||||
	// To live is to suffer,
 | 
					 | 
				
			||||||
	// The data is too big,
 | 
					 | 
				
			||||||
	// For the gosh-darn buffer.
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	#[error ("Data too big")]
 | 
					 | 
				
			||||||
	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")]
 | 
				
			||||||
| 
						 | 
					@ -60,24 +54,22 @@ impl <R: std::io::Read> Reader <R> {
 | 
				
			||||||
		Ok (())
 | 
							Ok (())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn length (r: &mut R) -> Result <u32> {
 | 
						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 (r: &mut R, buf: &mut [u8]) -> Result <u32> {
 | 
				
			||||||
		let l = Self::length (r)?;
 | 
							let l = Self::length (r)?;
 | 
				
			||||||
		let l = usize::try_from (l)?;
 | 
							if usize::try_from (l)? > buf.len () {
 | 
				
			||||||
		if l > limit {
 | 
								return Err (TlvError::CallerBufferTooSmall);
 | 
				
			||||||
			return Err (TlvError::DataTooBig);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		let mut v = vec! [0u8; l];
 | 
							r.read_exact (&mut buf [0..usize::try_from (l)?])?;
 | 
				
			||||||
		r.read_exact (&mut v)?;
 | 
					 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		Ok (v)
 | 
							Ok (l)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn u8 (r: &mut R) -> std::io::Result <u8> {
 | 
						pub fn u8 (r: &mut R) -> std::io::Result <u8> {
 | 
				
			||||||
| 
						 | 
					@ -90,17 +82,15 @@ impl <R: std::io::Read> Reader <R> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg (test)]
 | 
					#[cfg (test)]
 | 
				
			||||||
mod test {
 | 
					mod test {
 | 
				
			||||||
	use super::*;
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	#[test]
 | 
						#[test]
 | 
				
			||||||
	fn test_1 () -> Result <()> {
 | 
						fn test_1 () {
 | 
				
			||||||
		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).unwrap ();
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		let v = w.into_inner ();
 | 
							let v = w.into_inner ();
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
| 
						 | 
					@ -112,11 +102,11 @@ mod test {
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		let mut r = Cursor::new (v);
 | 
							let mut r = Cursor::new (v);
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		let buf = Reader::lv_bytes_to_vec (&mut r, 1024)?;
 | 
							let mut buf = vec! [0; 1024];
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		assert_eq! (buf.len (), b.len ());
 | 
							let bytes_read = super::Reader::lv_bytes (&mut r, &mut buf).unwrap ();
 | 
				
			||||||
		assert_eq! (b, &buf);
 | 
					 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		Ok (())
 | 
							assert_eq! (usize::try_from (bytes_read).unwrap (), b.len ());
 | 
				
			||||||
 | 
							assert_eq! (b, &buf [0..usize::try_from (bytes_read).unwrap ()]);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue