libnetkeeper 0.1.0

The netkeeper toolkits write in rust.
Documentation
use crypto::hash::{HasherBuilder, HasherType};
use common::utils::current_timestamp;
use common::dialer::Dialer;
use common::hex::ToHex;
use common::bytes::BytesAbleNum;

#[derive(Debug)]
pub enum GhcaDialerError {
    InvalidUsername(String),
    InvalidPassword(String),
}

#[derive(Debug)]
pub enum Configuration {
    SichuanMac,
}

#[derive(Debug)]
pub struct GhcaDialer {
    pub share_key: String,
    pub prefix: String,
    pub version: String,
}

impl GhcaDialer {
    fn new(share_key: &str, prefix: &str, version: &str) -> Self {
        GhcaDialer {
            share_key: share_key.to_string(),
            prefix: prefix.to_string(),
            version: version.to_string(),
        }
    }

    fn validate(username: &str, password: &str) -> Result<(), GhcaDialerError> {
        if username.len() > 60 {
            return Err(GhcaDialerError::InvalidUsername(username.to_string()));
        }
        if password.len() > 60 {
            return Err(GhcaDialerError::InvalidUsername(password.to_string()));
        }
        Ok(())
    }

    pub fn encrypt_account(&self,
                           username: &str,
                           password: &str,
                           fst_timestamp: Option<u32>,
                           sec_timestamp: Option<u32>)
                           -> Result<String, GhcaDialerError> {
        Self::validate(username, password)?;
        let name_len = username.len() as u32;
        let pwd_len = password.len() as u32;

        let fst_timestamp = fst_timestamp.unwrap_or_else(current_timestamp);
        let sec_timestamp = sec_timestamp.unwrap_or_else(current_timestamp);

        let mut cursor = fst_timestamp % pwd_len;
        if cursor < 1 {
            cursor += 1;
        }
        let match_flag = if cursor == pwd_len {
            1
        } else {
            0
        };

        let delta = cursor - match_flag;
        let md5_hash_prefix;
        {
            let mut md5 = HasherBuilder::build(HasherType::MD5);

            let prefix_len = delta + 1;
            let suffix_len = pwd_len - prefix_len;
            let pwd_prefix = &password[..prefix_len as usize];
            let pwd_suffix = &password[prefix_len as usize..pwd_len as usize];

            md5.update(&sec_timestamp.as_bytes_be());
            md5.update(self.share_key[..(60 - prefix_len) as usize].as_bytes());
            md5.update(pwd_prefix.as_bytes());
            md5.update(username.as_bytes());
            md5.update(self.share_key[..(64 - name_len - suffix_len) as usize].as_bytes());
            md5.update(pwd_suffix.as_bytes());

            let first_hashed_bytes = md5.finish();
            let mut md5 = HasherBuilder::build(HasherType::MD5);
            md5.update(&first_hashed_bytes);
            md5_hash_prefix = md5.finish()[..8].to_hex().to_uppercase();
        }

        let pwd_char_sum = password.as_bytes().iter().fold(0, |sum, x| sum + u32::from(*x));
        let pin = format!("{:04X}", delta ^ pwd_char_sum);
        Ok(format!("{}{:08X}{}{}{}{}",
                   self.prefix,
                   sec_timestamp,
                   self.version,
                   md5_hash_prefix,
                   pin,
                   username))
    }
}

impl Configuration {
    pub fn share_key(&self) -> &'static str {
        match *self {
            Configuration::SichuanMac => "aI0fC8RslXg6HXaKAUa6kpvcAXszvTcxYP8jmS9sBnVfIqTRdJS1eZNHmBjKN28j",
        }
    }

    pub fn prefix(&self) -> &'static str {
        match *self {
            _ => "~ghca",
        }
    }

    pub fn version(&self) -> &'static str {
        match *self {
            Configuration::SichuanMac => "2023",
        }
    }
}

impl Dialer for GhcaDialer {
    type C = Configuration;

    fn load_from_config(config: Self::C) -> Self {
        GhcaDialer::new(config.share_key(), config.prefix(), config.version())
    }
}