use super::bencode::BencodeValue; /// Decodes a 40-character hexadecimal string into a fixed-width 20-byte array. pub(super) fn decode_hex_20_array(input: &str) -> Result<[u8; 20], String> { if input.len() != 40 { return Err(format!( "expected 40 hex characters for 20-byte info-hash, got {}", input.len() )); } let mut out = [0_u8; 20]; for (index, chunk) in input.as_bytes().chunks_exact(2).enumerate() { let hi = decode_hex_nibble(chunk[0]) .ok_or_else(|| "info-hash contains non-hex characters".to_owned())?; let lo = decode_hex_nibble(chunk[1]) .ok_or_else(|| "info-hash contains non-hex characters".to_owned())?; out[index] = (hi << 4) | lo; } Ok(out) } /// Decodes one ASCII hexadecimal nibble. const fn decode_hex_nibble(byte: u8) -> Option { match byte { b'0'..=b'9' => Some(byte - b'0'), b'a'..=b'f' => Some(byte - b'a' + 10), b'A'..=b'F' => Some(byte - b'A' + 10), _ => None, } } /// Decodes torrent byte strings into owned lossy UTF-8 text. pub(super) fn bytes_to_string(bytes: &[u8]) -> String { String::from_utf8_lossy(bytes).into_owned() } /// Converts one torrent bencode value into human-readable string form. pub(super) fn value_to_string(value: &BencodeValue) -> String { match value { BencodeValue::Bytes(bytes) => bytes_to_string(bytes), BencodeValue::Int(value) => value.to_string(), BencodeValue::List(values) => values .iter() .map(value_to_string) .collect::>() .join(","), BencodeValue::Dict(map) => map .iter() .map(|(key, value)| format!("{key}={}", value_to_string(value))) .collect::>() .join("&"), } } /// Hex-encodes raw torrent bytes using lowercase hexadecimal. pub(super) fn hex_encode(bytes: &[u8]) -> String { const HEX: &[u8; 16] = b"0123456789abcdef"; let mut out = String::with_capacity(bytes.len() * 2); for &byte in bytes { out.push(char::from(HEX[usize::from(byte >> 4)])); out.push(char::from(HEX[usize::from(byte & 0x0f)])); } out } /// Saturates an integer-like torrent length field into `u64`. pub(super) fn i64_to_u64(value: i64) -> u64 { u64::try_from(value.max(0)).unwrap_or(u64::MAX) }