cargo fmt
							parent
							
								
									dc39531dc6
								
							
						
					
					
						commit
						319d8e6d29
					
				|  | @ -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), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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]] | ||||
| } | ||||
|  |  | |||
							
								
								
									
										550
									
								
								src/client.rs
								
								
								
								
							
							
						
						
									
										550
									
								
								src/client.rs
								
								
								
								
							|  | @ -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 (¶ms.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(¶ms.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
								
								
								
								
							
							
						
						
									
										184
									
								
								src/ip.rs
								
								
								
								
							|  | @ -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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										84
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										84
									
								
								src/main.rs
								
								
								
								
							|  | @ -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(()) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										593
									
								
								src/message.rs
								
								
								
								
							
							
						
						
									
										593
									
								
								src/message.rs
								
								
								
								
							|  | @ -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 (¶ms.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(¶ms.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(()) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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, | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										277
									
								
								src/server.rs
								
								
								
								
							
							
						
						
									
										277
									
								
								src/server.rs
								
								
								
								
							|  | @ -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 ¶ms.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 ¶ms.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?; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										207
									
								
								src/tlv.rs
								
								
								
								
							
							
						
						
									
										207
									
								
								src/tlv.rs
								
								
								
								
							|  | @ -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(()) | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 _
						_