// Shared XML, URI, and hash parsing helpers. #[cfg(test)] use snapshot_apply::apply_snapshot; use snapshot_apply::{apply_snapshot_from_bufread, fetch_snapshot_into_tempfile}; fn parse_rrdp_xml(xml: &[u8]) -> Result, RrdpError> { if xml.iter().any(|&b| b > 0x7F) { return Err(RrdpError::NotAscii); } let s = std::str::from_utf8(xml).map_err(|e| RrdpError::Xml(e.to_string()))?; roxmltree::Document::parse(s).map_err(|e| RrdpError::Xml(e.to_string())) } fn validate_root_common(root: &roxmltree::Node<'_, '_>) -> Result<(), RrdpError> { let ns = root.default_namespace().unwrap_or("").to_string(); if ns != RRDP_XMLNS { return Err(RrdpError::InvalidNamespace(ns)); } let version = root.attribute("version").unwrap_or(""); if version != "1" { return Err(RrdpError::InvalidVersion(version.to_string())); } Ok(()) } fn parse_uuid_attr(root: &roxmltree::Node<'_, '_>, name: &'static str) -> Result { let s = root.attribute(name).unwrap_or(""); Uuid::parse_str(s).map_err(|_e| RrdpError::InvalidSessionId(s.to_string())) } fn parse_u64_attr(root: &roxmltree::Node<'_, '_>, name: &'static str) -> Result { let s = root.attribute(name).unwrap_or(""); parse_u64_str(s) } fn parse_u64_str(s: &str) -> Result { let v = s .parse::() .map_err(|_e| RrdpError::InvalidSerial(s.to_string()))?; if v == 0 { return Err(RrdpError::InvalidSerial(s.to_string())); } Ok(v) } fn parse_sha256_hex_impl(s: &str, invalid: fn(String) -> RrdpError) -> Result<[u8; 32], RrdpError> { let bytes = hex::decode(s).map_err(|_e| invalid(s.to_string()))?; if bytes.len() != 32 { return Err(invalid(s.to_string())); } let mut out = [0u8; 32]; out.copy_from_slice(&bytes); Ok(out) } fn parse_sha256_hex_snapshot(s: &str) -> Result<[u8; 32], RrdpError> { parse_sha256_hex_impl(s, RrdpError::SnapshotHashInvalid) } fn parse_sha256_hex_delta_ref(s: &str) -> Result<[u8; 32], RrdpError> { parse_sha256_hex_impl(s, RrdpError::DeltaRefHashInvalid) } fn parse_sha256_hex_delta_publish(s: &str) -> Result<[u8; 32], RrdpError> { parse_sha256_hex_impl(s, RrdpError::DeltaPublishHashInvalid) } fn parse_sha256_hex_delta_withdraw(s: &str) -> Result<[u8; 32], RrdpError> { parse_sha256_hex_impl(s, RrdpError::DeltaWithdrawHashInvalid) } fn collect_element_text(node: &roxmltree::Node<'_, '_>) -> Option { let mut out = String::new(); for child in node.children() { if child.is_text() { out.push_str(child.text().unwrap_or("")); } } if out.is_empty() { None } else { Some(out) } } fn strip_all_ascii_whitespace(s: &str) -> String { s.chars().filter(|c| !c.is_ascii_whitespace()).collect() }