137 lines
4.7 KiB
Rust
137 lines
4.7 KiB
Rust
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<u64, String> {
|
|
// 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<u8>) -> 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<u8> {
|
|
// Parse the IP string into the standard IP enum before encoding it.
|
|
match ip_str.parse::<IpAddr>() {
|
|
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<u8>) -> 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<Vec<u8>, 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)
|
|
}
|