torrust-actix 4.2.3

A rich, fast and efficient Bittorrent Tracker.
use crate::common::structs::compressed_bytes::COMPRESSION;
use crate::common::structs::compression_state::CompressionState;
use crate::common::structs::custom_error::CustomError;
use crate::common::types::QueryValues;
use crate::config::enums::compression_algorithm::CompressionAlgorithm;
use crate::config::structs::configuration::Configuration;
use crate::security::security::MAX_PERCENT_DECODED_SIZE;
use async_std::future;
use fern::colors::{
    Color,
    ColoredLevelConfig
};
use log::info;
use sha1::Digest;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Formatter;
use std::time::{
    Duration,
    SystemTime
};
use tokio_shutdown::Shutdown;

#[inline]
pub fn parse_query(query: Option<String>) -> Result<HashMap<String, QueryValues>, CustomError> {
    let mut queries: HashMap<String, QueryValues> = HashMap::with_capacity(12);
    if let Some(result) = query {
        for query_item in result.split('&') {
            if query_item.is_empty() {
                continue;
            }
            if let Some(equal_pos) = query_item.find('=') {
                let (key_part, value_part) = query_item.split_at(equal_pos);
                let key_name_raw = key_part;
                let value_data_raw = &value_part[1..];
                let key_name = if key_name_raw.contains('%') || key_name_raw.contains('+') {
                    percent_encoding::percent_decode_str(key_name_raw)
                        .decode_utf8_lossy()
                        .to_lowercase()
                } else {
                    key_name_raw.to_ascii_lowercase()
                };
                if key_name.is_empty() {
                    continue;
                }
                let value_data = percent_encoding::percent_decode_str(value_data_raw).collect::<Vec<u8>>();
                if value_data.len() > MAX_PERCENT_DECODED_SIZE {
                    return Err(CustomError::new(&format!(
                        "Percent-decoded value exceeds maximum size of {MAX_PERCENT_DECODED_SIZE} bytes"
                    )));
                }
                queries
                    .entry(key_name)
                    .or_default()
                    .push(value_data);
            } else {
                let key_name = if query_item.contains('%') || query_item.contains('+') {
                    percent_encoding::percent_decode_str(query_item)
                        .decode_utf8_lossy()
                        .to_lowercase()
                } else {
                    query_item.to_ascii_lowercase()
                };
                if key_name.is_empty() {
                    continue;
                }
                queries
                    .entry(key_name)
                    .or_default()
                    .push(Vec::new());
            }
        }
    }
    Ok(queries)
}

pub fn udp_check_host_and_port_used(bind_address: String) {
    if cfg!(target_os = "windows") && let Err(data) = std::net::UdpSocket::bind(&bind_address) {
        sentry::capture_error(&data);
        panic!("Unable to bind to {} ! Exiting...", &bind_address);
    }
}

pub(crate) fn bin2hex(data: &[u8; 20], f: &mut Formatter) -> fmt::Result {
    let mut chars = [0u8; 40];
    binascii::bin2hex(data, &mut chars).expect("failed to hexlify");
    write!(f, "{}", std::str::from_utf8(&chars).unwrap())
}

pub fn hex2bin(data: String) -> Result<[u8; 20], CustomError> {
    hex::decode(data)
        .map_err(|data| {
            sentry::capture_error(&data);
            CustomError::new("error converting hex to bin")
        })
        .and_then(|hash_result| {
            hash_result
                .get(..20)
                .and_then(|slice| slice.try_into().ok())
                .ok_or_else(|| CustomError::new("invalid hex length"))
        })
}

pub fn print_type<T>(_: &T) {
    println!("{:?}", std::any::type_name::<T>());
}

pub fn return_type<T>(_: &T) -> String {
    format!("{:?}", std::any::type_name::<T>())
}

pub fn equal_string_check(source: &str, check: &str) -> bool {
    if source == check {
        return true;
    }
    println!("Source: {source}");
    println!("Check:  {check}");
    false
}

pub fn setup_logging(config: &Configuration) {
    let level = match config.log_level.as_str() {
        "off" => log::LevelFilter::Off,
        "trace" => log::LevelFilter::Trace,
        "debug" => log::LevelFilter::Debug,
        "info" => log::LevelFilter::Info,
        "warn" => log::LevelFilter::Warn,
        "error" => log::LevelFilter::Error,
        _ => {
            panic!("Unknown log level encountered: '{}'", config.log_level.as_str());
        }
    };
    let colors = ColoredLevelConfig::new()
        .trace(Color::Cyan)
        .debug(Color::Magenta)
        .info(Color::Green)
        .warn(Color::Yellow)
        .error(Color::Red);
    fern::Dispatch::new()
        .format(move |out, message, record| {
            out.finish(format_args!(
                "{} [{:width$}][{}] {}",
                chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.9f"),
                colors.color(record.level()),
                record.target(),
                message,
                width = 5
            ));
        })
        .level(level)
        .chain(std::io::stdout())
        .apply()
        .unwrap_or_else(|_| panic!("Failed to initialize logging."));
    info!("logging initialized.");
}

#[inline]
pub fn current_time() -> u64 {
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .expect("System time before UNIX epoch")
        .as_secs()
}

#[inline]
pub fn convert_int_to_bytes(number: &u64) -> Vec<u8> {
    let bytes = number.to_be_bytes();
    let leading_zeros = number.leading_zeros() as usize / 8;
    bytes[leading_zeros..].to_vec()
}

#[inline]
pub fn convert_bytes_to_int(array: &[u8]) -> u64 {
    let mut array_fixed = [0u8; 8];
    let len = array.len().min(8);
    array_fixed[8 - len..].copy_from_slice(&array[..len]);
    u64::from_be_bytes(array_fixed)
}

pub async fn shutdown_waiting(timeout: Duration, shutdown_handler: Shutdown) -> bool {
    future::timeout(timeout, shutdown_handler.handle())
        .await
        .is_ok()
}

pub fn hash_id(id: &str) -> [u8; 20] {
    let mut hasher = sha1::Sha1::new();
    hasher.update(id.as_bytes());
    <[u8; 20]>::try_from(hasher.finalize().as_slice()).unwrap()
}

#[inline(always)]
pub fn hex_to_nibble(c: u8) -> u8 {
    match c {
        b'0'..=b'9' => c - b'0',
        b'a'..=b'f' => c - b'a' + 10,
        b'A'..=b'F' => c - b'A' + 10,
        _ => 0xFF,
    }
}

#[inline(always)]
pub fn hex_char_to_nibble(c: u8) -> u8 {
    match c {
        b'0'..=b'9' => c - b'0',
        b'a'..=b'f' => c - b'a' + 10,
        b'A'..=b'F' => c - b'A' + 10,
        _ => 0xFF,
    }
}

pub fn init_compression(enabled: bool, algorithm: CompressionAlgorithm, level: u32) {
    let _ = COMPRESSION.set(CompressionState { enabled, algorithm, level });
}