use crate::error::SxurlError;
use crate::url::normalizer::normalize_url;
use crate::url::psl::extract_url_components;
use crate::core::packer::{pack_sxurl, pack_sxurl_with_hashes, sxurl_to_hex, hex_to_sxurl};
use crate::core::hasher::ComponentHasher;
use crate::types::UrlComponents;
#[derive(Debug, Clone)]
struct ComputedHashes {
tld_hash: u64,
domain_hash: u64,
subdomain_hash: u64,
path_hash: u64,
params_hash: u64,
fragment_hash: u64,
}
fn compute_component_hashes(url: &str) -> Result<(UrlComponents, ComputedHashes), SxurlError> {
let normalized_url = normalize_url(url)?;
let components = extract_url_components(&normalized_url)?;
let tld_hash = ComponentHasher::hash_tld(&components.tld)?;
let domain_hash = ComponentHasher::hash_domain(&components.domain)?;
let subdomain_hash = if components.subdomain.is_empty() {
0
} else {
ComponentHasher::hash_subdomain(&components.subdomain)?
};
let path_hash = ComponentHasher::hash_path(&components.path)?;
let params_hash = if components.query.is_empty() {
0
} else {
ComponentHasher::hash_params(&components.query)?
};
let fragment_hash = if components.fragment.is_empty() {
0
} else {
ComponentHasher::hash_fragment(&components.fragment)?
};
let hashes = ComputedHashes {
tld_hash,
domain_hash,
subdomain_hash,
path_hash,
params_hash,
fragment_hash,
};
Ok((components, hashes))
}
pub fn encode_url(url: &str) -> Result<[u8; 32], SxurlError> {
let normalized_url = normalize_url(url)?;
let components = extract_url_components(&normalized_url)?;
pack_sxurl(&components)
}
pub fn encode_url_to_hex(url: &str) -> Result<String, SxurlError> {
let sxurl_bytes = encode_url(url)?;
Ok(sxurl_to_hex(&sxurl_bytes))
}
pub fn encode_host_to_hex(url: &str) -> Result<String, SxurlError> {
let (components, hashes) = compute_component_hashes(url)?;
let mut domain_components = components.clone();
domain_components.port = 0;
domain_components.path = "/".to_string(); domain_components.query = String::new();
domain_components.fragment = String::new();
let sxurl_bytes = pack_sxurl_with_hashes(
&domain_components,
hashes.tld_hash,
hashes.domain_hash,
hashes.subdomain_hash,
0, 0, 0, )?;
Ok(sxurl_to_hex(&sxurl_bytes))
}
pub fn extract_host_from_full_sxurl(full_sxurl: &str) -> Result<String, SxurlError> {
if full_sxurl.len() != 64 {
return Err(SxurlError::InvalidLength);
}
let scheme = &full_sxurl[0..1];
let reserved = &full_sxurl[1..2];
let tld_hash = &full_sxurl[2..9];
let domain_hash = &full_sxurl[9..24];
let subdomain_hash = &full_sxurl[24..32];
let original_flags = u8::from_str_radix(&full_sxurl[32..34], 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
let subdomain_flag = original_flags & 0x80; let host_flags = format!("{:02x}", subdomain_flag);
Ok(format!(
"{}{}{}{}{}{}{}",
scheme, reserved, tld_hash, domain_hash, subdomain_hash, host_flags, "000000000000000000000000000000" ))
}
pub fn extract_host_from_full_bytes(full_sxurl: &[u8; 32]) -> Result<[u8; 32], SxurlError> {
let full_hex = sxurl_to_hex(full_sxurl);
let host_hex = extract_host_from_full_sxurl(&full_hex)?;
hex_to_sxurl(&host_hex)
}
pub struct SxurlEncoder {
}
impl SxurlEncoder {
pub fn new() -> Self {
Self {}
}
pub fn encode(&self, url: &str) -> Result<[u8; 32], SxurlError> {
encode_url(url)
}
pub fn encode_to_hex(&self, url: &str) -> Result<String, SxurlError> {
encode_url_to_hex(url)
}
}
impl Default for SxurlEncoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_url_basic() {
let result = encode_url("https://example.com/");
assert!(result.is_ok());
let sxurl = result.unwrap();
assert_eq!(sxurl.len(), 32);
}
#[test]
fn test_encode_url_to_hex() {
let result = encode_url_to_hex("https://example.com/");
assert!(result.is_ok());
let hex = result.unwrap();
assert_eq!(hex.len(), 64);
assert!(hex.starts_with("00")); }
#[test]
fn test_encode_url_with_params() {
let result = encode_url_to_hex("https://google.com/search?q=test");
assert!(result.is_ok());
let hex = result.unwrap();
assert!(hex.starts_with("00"));
let flags = u8::from_str_radix(&hex[32..34], 16).unwrap();
assert_eq!(flags & 0x10, 0x10); }
#[test]
fn test_encode_invalid_scheme() {
let result = encode_url("ws://example.com/");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), SxurlError::InvalidScheme));
}
#[test]
fn test_encoder_struct() {
let encoder = SxurlEncoder::new();
let result = encoder.encode_to_hex("https://docs.rs/");
assert!(result.is_ok());
let hex = result.unwrap();
assert_eq!(hex.len(), 64);
}
#[test]
fn test_encoder_default() {
let encoder = SxurlEncoder::default();
let result = encoder.encode("https://example.com/");
assert!(result.is_ok());
}
#[test]
fn test_host_extraction_from_full_sxurl() {
let full_url = "https://api.github.com/repos?page=1#section";
let full_sxurl_hex = encode_url_to_hex(full_url).unwrap();
let host_sxurl_hex = extract_host_from_full_sxurl(&full_sxurl_hex).unwrap();
assert_eq!(host_sxurl_hex.len(), 64);
assert!(host_sxurl_hex.ends_with("000000000000000000000000000000"));
assert_eq!(&host_sxurl_hex[0..1], &full_sxurl_hex[0..1]); assert_eq!(&host_sxurl_hex[1..2], &full_sxurl_hex[1..2]); assert_eq!(&host_sxurl_hex[2..9], &full_sxurl_hex[2..9]); assert_eq!(&host_sxurl_hex[9..24], &full_sxurl_hex[9..24]); assert_eq!(&host_sxurl_hex[24..32], &full_sxurl_hex[24..32]);
let other_url = "https://api.github.com/users";
let other_full_hex = encode_url_to_hex(other_url).unwrap();
let other_host_hex = extract_host_from_full_sxurl(&other_full_hex).unwrap();
assert_eq!(host_sxurl_hex, other_host_hex);
let full_sxurl_bytes = encode_url(full_url).unwrap();
let host_sxurl_bytes = extract_host_from_full_bytes(&full_sxurl_bytes).unwrap();
let host_from_bytes_hex = sxurl_to_hex(&host_sxurl_bytes);
assert_eq!(host_sxurl_hex, host_from_bytes_hex);
println!("✓ Host extraction test passed!");
println!(" Full SXURL: {}", full_sxurl_hex);
println!(" Host SXURL: {}", host_sxurl_hex);
}
#[test]
fn test_normalize_and_encode() {
let result1 = encode_url_to_hex("HTTPS://EXAMPLE.COM/Path");
let result2 = encode_url_to_hex("https://example.com/Path");
assert!(result1.is_ok());
assert!(result2.is_ok());
assert_eq!(result1.unwrap(), result2.unwrap());
}
#[test]
fn test_idna_encoding() {
let result = encode_url("https://café.com/test");
assert!(result.is_ok());
}
#[test]
fn test_psl_integration() {
let result = encode_url_to_hex("https://api.example.co.uk/test");
assert!(result.is_ok());
let hex = result.unwrap();
let flags = u8::from_str_radix(&hex[32..34], 16).unwrap();
let sub_present = (flags & (1 << 7)) != 0;
assert!(sub_present, "Subdomain flag should be set for api.example.co.uk");
}
#[test]
fn test_encode_domain_to_hex_basic() {
let result = encode_host_to_hex("https://api.github.com/repos?page=1");
assert!(result.is_ok());
let domain_hex = result.unwrap();
assert_eq!(domain_hex.len(), 64);
assert!(domain_hex.ends_with("000000000000000000000000000000"));
assert!(domain_hex.starts_with("00")); }
#[test]
fn test_domain_sxurl_same_domain_different_paths() {
let urls = vec![
"https://api.github.com/repos",
"https://api.github.com/users/john",
"https://api.github.com/repos?page=1&sort=name",
"https://api.github.com/v3/docs#authentication",
"https://api.github.com:443/explicit/port",
];
let domain_sxurls: Vec<String> = urls.iter()
.map(|url| encode_host_to_hex(url).unwrap())
.collect();
for i in 1..domain_sxurls.len() {
assert_eq!(domain_sxurls[0], domain_sxurls[i],
"URLs {} and {} should produce identical domain SXURLs", urls[0], urls[i]);
}
assert!(domain_sxurls[0].ends_with("000000000000000000000000000000"));
}
#[test]
fn test_domain_sxurl_different_subdomains() {
let api = encode_host_to_hex("https://api.github.com/repos").unwrap();
let docs = encode_host_to_hex("https://docs.github.com/guides").unwrap();
let www = encode_host_to_hex("https://www.github.com/about").unwrap();
let no_sub = encode_host_to_hex("https://github.com/home").unwrap();
assert_ne!(api, docs);
assert_ne!(api, www);
assert_ne!(docs, www);
assert_ne!(api, no_sub);
assert!(api.ends_with("000000000000000000000000000000"));
assert!(docs.ends_with("000000000000000000000000000000"));
assert!(www.ends_with("000000000000000000000000000000"));
assert!(no_sub.ends_with("000000000000000000000000000000"));
}
#[test]
fn test_domain_sxurl_different_schemes() {
let https = encode_host_to_hex("https://example.com/").unwrap();
let http = encode_host_to_hex("http://example.com/").unwrap();
let ftp = encode_host_to_hex("ftp://example.com/").unwrap();
assert_ne!(https, http);
assert_ne!(https, ftp);
assert_ne!(http, ftp);
let https_scheme = &https[0..1];
let http_scheme = &http[0..1];
let ftp_scheme = &ftp[0..1];
assert_eq!(https_scheme, "0"); assert_eq!(http_scheme, "1"); assert_eq!(ftp_scheme, "2"); }
#[test]
fn test_domain_sxurl_header_flags() {
let with_sub = encode_host_to_hex("https://api.github.com").unwrap();
let without_sub = encode_host_to_hex("https://github.com").unwrap();
let with_sub_flags = u8::from_str_radix(&with_sub[32..34], 16).unwrap();
let without_sub_flags = u8::from_str_radix(&without_sub[32..34], 16).unwrap();
let with_sub_flag = (with_sub_flags & (1 << 7)) != 0;
let without_sub_flag = (without_sub_flags & (1 << 7)) != 0;
assert!(with_sub_flag, "Should have subdomain flag set");
assert!(!without_sub_flag, "Should not have subdomain flag set");
let params_flag = (with_sub_flags & (1 << 4)) != 0;
let frag_flag = (with_sub_flags & (1 << 3)) != 0;
let path_flag = (with_sub_flags & (1 << 5)) != 0;
assert!(!params_flag, "Host SXURL should not have params flag");
assert!(!frag_flag, "Host SXURL should not have fragment flag");
assert!(!path_flag, "Host SXURL should not have path flag");
}
#[test]
fn test_domain_vs_full_sxurl_comparison() {
let url = "https://api.github.com/repos/rust-lang/rust?tab=readme#installation";
let domain_sxurl = encode_host_to_hex(url).unwrap();
let full_sxurl = encode_url_to_hex(url).unwrap();
assert_ne!(domain_sxurl, full_sxurl);
assert_eq!(&domain_sxurl[3..30], &full_sxurl[3..30]);
assert!(domain_sxurl.ends_with("000000000000000000000000000000"));
assert!(!full_sxurl.ends_with("000000000000000000000000000000"));
}
#[test]
fn test_compute_component_hashes_efficiency() {
let url = "https://api.github.com/repos/rust-lang/rust";
let result = compute_component_hashes(url);
assert!(result.is_ok());
let (components, hashes) = result.unwrap();
assert_eq!(components.scheme, "https");
assert_eq!(components.domain, "github");
assert_eq!(components.subdomain, "api");
assert_eq!(components.tld, "com");
assert!(hashes.tld_hash > 0);
assert!(hashes.domain_hash > 0);
assert!(hashes.subdomain_hash > 0);
assert!(hashes.path_hash > 0);
}
#[test]
fn test_domain_sxurl_with_complex_tld() {
let uk_url = "https://api.example.co.uk/test";
let com_url = "https://api.example.com/test";
let uk_domain = encode_host_to_hex(uk_url).unwrap();
let com_domain = encode_host_to_hex(com_url).unwrap();
assert_ne!(uk_domain, com_domain);
assert!(uk_domain.ends_with("000000000000000000000000000000"));
assert!(com_domain.ends_with("000000000000000000000000000000"));
}
}