Contractless/src/common/binary_conversions.rs

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)
}