use crate::error::SxurlError;
use crate::types::SxurlHeader;
use crate::core::packer::hex_to_sxurl;
#[derive(Debug, Clone, PartialEq)]
pub struct DecodedSxurl {
pub header: SxurlHeader,
pub tld_hash: u64,
pub domain_hash: u64,
pub subdomain_hash: u64,
pub port: u16,
pub path_hash: u64,
pub params_hash: u64,
pub fragment_hash: u64,
}
impl DecodedSxurl {
pub fn tld_hex_slice(&self) -> String {
format!("{:04x}", self.tld_hash)
}
pub fn domain_hex_slice(&self) -> String {
format!("{:015x}", self.domain_hash)
}
pub fn subdomain_hex_slice(&self) -> String {
format!("{:08x}", self.subdomain_hash)
}
pub fn port_hex_slice(&self) -> String {
format!("{:04x}", self.port)
}
pub fn path_hex_slice(&self) -> String {
format!("{:015x}", self.path_hash)
}
pub fn params_hex_slice(&self) -> String {
format!("{:09x}", self.params_hash)
}
pub fn fragment_hex_slice(&self) -> String {
format!("{:06x}", self.fragment_hash)
}
}
pub fn decode_hex(hex_str: &str) -> Result<DecodedSxurl, SxurlError> {
let sxurl_bytes = hex_to_sxurl(hex_str)?;
decode_bytes(&sxurl_bytes)
}
pub fn decode_bytes(sxurl_bytes: &[u8; 32]) -> Result<DecodedSxurl, SxurlError> {
let hex_str = hex::encode(sxurl_bytes);
let header_hex = &hex_str[0..3];
let header_value = u16::from_str_radix(header_hex, 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
let version = (header_value >> 8) & 0xF;
if version != 1 {
return Err(SxurlError::UnsupportedVersion(version));
}
let scheme_code = (header_value >> 5) & 0x7;
let scheme = match scheme_code {
0 => "https".to_string(),
1 => "http".to_string(),
2 => "ftp".to_string(),
_ => return Err(SxurlError::InvalidScheme),
};
let flags = header_value & 0x1F;
let sub_present = (flags & (1 << 4)) != 0;
let params_present = (flags & (1 << 3)) != 0;
let frag_present = (flags & (1 << 2)) != 0;
let port_present = (flags & (1 << 1)) != 0;
let header = SxurlHeader::new(scheme, sub_present, params_present, frag_present, port_present);
let tld_hash = u64::from_str_radix(&hex_str[3..7], 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
let domain_hash = u64::from_str_radix(&hex_str[7..22], 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
let subdomain_hash = u64::from_str_radix(&hex_str[22..30], 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
let port = u16::from_str_radix(&hex_str[30..34], 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
let path_hash = u64::from_str_radix(&hex_str[34..49], 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
let params_hash = u64::from_str_radix(&hex_str[49..58], 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
let fragment_hash = u64::from_str_radix(&hex_str[58..64], 16)
.map_err(|_| SxurlError::InvalidHexCharacter)?;
Ok(DecodedSxurl {
header,
tld_hash,
domain_hash,
subdomain_hash,
port,
path_hash,
params_hash,
fragment_hash,
})
}
pub fn matches_component(sxurl_hex: &str, component: &str, value: &str) -> Result<bool, SxurlError> {
use crate::core::hasher::ComponentHasher;
let decoded = decode_hex(sxurl_hex)?;
let component_hash = match component {
"tld" => {
let expected = ComponentHasher::hash_tld(value)?;
decoded.tld_hash == expected
}
"domain" => {
let expected = ComponentHasher::hash_domain(value)?;
decoded.domain_hash == expected
}
"sub" => {
if value.is_empty() {
decoded.subdomain_hash == 0
} else {
let expected = ComponentHasher::hash_subdomain(value)?;
decoded.subdomain_hash == expected
}
}
"path" => {
let expected = ComponentHasher::hash_path(value)?;
decoded.path_hash == expected
}
"params" => {
if value.is_empty() {
decoded.params_hash == 0
} else {
let expected = ComponentHasher::hash_params(value)?;
decoded.params_hash == expected
}
}
"frag" => {
if value.is_empty() {
decoded.fragment_hash == 0
} else {
let expected = ComponentHasher::hash_fragment(value)?;
decoded.fragment_hash == expected
}
}
_ => return Err(SxurlError::InternalError),
};
Ok(component_hash)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::encoder::encode_url_to_hex;
#[test]
fn test_decode_basic() {
let url = "https://example.com/";
let hex = encode_url_to_hex(url).unwrap();
let decoded = decode_hex(&hex).unwrap();
assert_eq!(decoded.header.version, 1);
assert_eq!(decoded.header.scheme, "https");
assert!(!decoded.header.sub_present);
assert!(!decoded.header.params_present);
assert!(!decoded.header.frag_present);
assert!(!decoded.header.port_present);
}
#[test]
fn test_decode_with_flags() {
let url = "https://api.example.com:8443/search?q=test#results";
let hex = encode_url_to_hex(url).unwrap();
let decoded = decode_hex(&hex).unwrap();
assert_eq!(decoded.header.scheme, "https");
assert!(decoded.header.sub_present);
assert!(decoded.header.params_present);
assert!(decoded.header.frag_present);
assert!(decoded.header.port_present);
assert_eq!(decoded.port, 8443);
}
#[test]
fn test_hex_slices() {
let url = "https://docs.rs/";
let hex = encode_url_to_hex(url).unwrap();
let decoded = decode_hex(&hex).unwrap();
assert_eq!(decoded.tld_hex_slice().len(), 4);
assert_eq!(decoded.domain_hex_slice().len(), 15);
assert_eq!(decoded.subdomain_hex_slice().len(), 8);
assert_eq!(decoded.port_hex_slice().len(), 4);
assert_eq!(decoded.path_hex_slice().len(), 15);
assert_eq!(decoded.params_hex_slice().len(), 9);
assert_eq!(decoded.fragment_hex_slice().len(), 6);
}
#[test]
fn test_matches_component() {
let url = "https://docs.rs/";
let hex = encode_url_to_hex(url).unwrap();
assert!(matches_component(&hex, "tld", "rs").unwrap());
assert!(matches_component(&hex, "domain", "docs").unwrap());
assert!(matches_component(&hex, "sub", "").unwrap());
assert!(matches_component(&hex, "path", "/").unwrap());
assert!(!matches_component(&hex, "tld", "com").unwrap());
assert!(!matches_component(&hex, "domain", "google").unwrap());
}
#[test]
fn test_decode_invalid_hex() {
let result = decode_hex("invalid");
assert!(result.is_err());
}
#[test]
fn test_decode_wrong_length() {
let result = decode_hex("123456"); assert!(result.is_err());
}
#[test]
fn test_round_trip() {
let url = "https://api.example.co.uk:8443/search?q=test#results";
let hex = encode_url_to_hex(url).unwrap();
let decoded = decode_hex(&hex).unwrap();
assert_eq!(decoded.header.scheme, "https");
assert!(decoded.header.sub_present);
assert!(decoded.header.params_present);
assert!(decoded.header.frag_present);
assert!(decoded.header.port_present);
assert_eq!(decoded.port, 8443);
}
}