use crate::IpAddr; use crate::Ipv6Addr; // Convert a 256-bit hex hash into the reduced u64 value used by // the mining difficulty comparison. pub async fn hex_to_u64(hex_string: &str) -> Result { // The input must be one full 32-byte hash encoded as 64 hex characters. if hex_string.len() != 64 { return Err("Invalid hash length. Must be 64 characters.".to_string()); } let mut extracted_string = String::new(); // Sample across the whole hash instead of using only the front bytes. for i in (0..64).step_by(4) { extracted_string.push(hex_string.chars().nth(i).unwrap()); } // Interpret the sampled hex characters as the mining comparison value. if let Ok(value) = u64::from_str_radix(&extracted_string, 16) { Ok(value) } else { Err("Failed to convert to u64.".to_string()) } } // Convert raw bytes into UTF-8 text for command payloads that are // expected to be plain strings. pub fn binary_to_string(binary_data: Vec) -> String { match String::from_utf8(binary_data.clone()) { Ok(s) => s, Err(_) => { "Invalid UTF-8 Data".to_string() } } } pub fn ip_to_binary(ip_str: &str) -> Vec { // Parse the IP string into the standard IP enum before encoding it. match ip_str.parse::() { Ok(IpAddr::V4(ipv4_addr)) => { // Store IPv4 addresses in the same 16-byte field by // prefixing the 4-byte address with twelve zero bytes. let mut bytes = vec![0u8; 12]; bytes.extend_from_slice(&ipv4_addr.octets()); bytes } Ok(IpAddr::V6(ipv6_addr)) => { // IPv6 already fits the fixed 16-byte network layout. ipv6_addr.octets().to_vec() } Err(_) => { // Invalid IP strings are rejected by returning no bytes. Vec::new() } } } // Convert the fixed 16-byte network IP layout back into visible text. pub fn binary_to_ip(bytes: Vec) -> String { if bytes.len() != 16 { return "Invalid input length".to_string(); } // Twelve leading zero bytes identify an IPv4 address stored in // the final four bytes of the fixed-width field. if bytes[0..12].iter().all(|&b| b == 0) { let ipv4_addr = (u32::from(bytes[12]) << 24 | u32::from(bytes[13]) << 16 | u32::from(bytes[14]) << 8 | u32::from(bytes[15])) .into(); format!("{}", IpAddr::V4(ipv4_addr)) } else { // Non-IPv4 layouts are decoded as a full IPv6 address. let mut ipv6_bytes = [0u8; 16]; ipv6_bytes.copy_from_slice(&bytes); format!("{}", IpAddr::V6(Ipv6Addr::from(ipv6_bytes))) } } // Convert the fixed 18-byte network endpoint layout into ip:port text. pub fn binary_to_ip_port(bytes: &[u8]) -> String { if bytes.len() != 18 { return String::from("Error: Invalid binary length"); } // The first 16 bytes are the IP address and the final two are the port. let ip_bytes = &bytes[..16]; let port_bytes = &bytes[16..]; let ip = binary_to_ip(ip_bytes.to_vec()); let port_bytes: [u8; 2] = match port_bytes.try_into() { Ok(bytes) => bytes, Err(_) => { return String::from("Error: Invalid port_bytes length"); } }; let port = u16::from_le_bytes(port_bytes); format!("{ip}:{port}") } // Convert ip:port text into the fixed 18-byte endpoint layout used on the wire. pub fn ip_port_to_binary(ip_port: &str) -> Result, String> { // Split from the right so IPv6 addresses with colons still parse correctly. let (ip_part, port_part) = ip_port .rsplit_once(':') .ok_or_else(|| String::from("Invalid format"))?; // Accept bracketed IPv6 endpoint strings such as [::1]:50000. let normalized_ip = ip_part .strip_prefix('[') .and_then(|ip| ip.strip_suffix(']')) .unwrap_or(ip_part); let ip: IpAddr = normalized_ip .parse() .map_err(|_| String::from("Invalid IP address"))?; let port: u16 = port_part .parse() .map_err(|_| String::from("Invalid port"))?; // Store IPv4 and IPv6 in the same fixed-width 16-byte address slot. let ip_bytes = match ip { IpAddr::V4(ipv4) => { let mut ipv6_bytes = [0u8; 16]; ipv6_bytes[12..].copy_from_slice(&ipv4.octets()); ipv6_bytes.to_vec() } IpAddr::V6(ipv6) => ipv6.octets().to_vec(), }; // Ports are little-endian because the rest of the binary protocol // stores integer fields in little-endian order. let mut result = ip_bytes; result.extend_from_slice(&port.to_le_bytes()); Ok(result) }