use std::fmt;
use sciparse::address::ip_addr::ScionIpAddr;
use crate::{error::Error, request::Request};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct Origin {
pub(crate) host: String,
pub(crate) port: u16,
pub(crate) candidates: Candidates,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Candidates {
Dns,
Static(Vec<ScionIpAddr>),
}
impl Origin {
pub(crate) fn from_request(request: &Request) -> Result<Origin, Error> {
let url = request.url();
let host = url
.host_str()
.ok_or_else(|| {
Error::InvalidRequest {
reason: "URL has no host".into(),
}
})?
.to_string();
let port = url.port_or_known_default().unwrap_or(443);
let candidates = match request.targets() {
Some(targets) => Candidates::Static(targets.to_vec()),
None => Candidates::Dns,
};
Ok(Origin {
host,
port,
candidates,
})
}
}
impl fmt::Display for Origin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.host, self.port)?;
if let Candidates::Static(targets) = &self.candidates {
write!(f, " ({} static candidate(s))", targets.len())?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use super::*;
fn addr(last_octet: u8) -> ScionIpAddr {
ScionIpAddr::new(
"1-ff00:0:110".parse().unwrap(),
Ipv4Addr::new(10, 0, 0, last_octet).into(),
)
}
#[test]
fn port_defaults_to_443() {
let req = Request::get("https://example.org/x").build().unwrap();
let origin = Origin::from_request(&req).unwrap();
assert_eq!(origin.host, "example.org");
assert_eq!(origin.port, 443);
assert_eq!(origin.candidates, Candidates::Dns);
}
#[test]
fn explicit_port_is_used() {
let req = Request::get("https://example.org:8443/").build().unwrap();
let origin = Origin::from_request(&req).unwrap();
assert_eq!(origin.port, 8443);
}
#[test]
fn same_url_same_origin_regardless_of_path() {
let a = Origin::from_request(&Request::get("https://example.org/a").build().unwrap());
let b = Origin::from_request(&Request::get("https://example.org/b?x=1").build().unwrap());
assert_eq!(a.unwrap(), b.unwrap());
}
#[test]
fn target_order_does_not_fragment_the_key() {
let a = Request::get("https://example.org/")
.targets(vec![addr(1), addr(2)])
.build()
.unwrap();
let b = Request::get("https://example.org/")
.targets(vec![addr(2), addr(1)])
.build()
.unwrap();
assert_eq!(
Origin::from_request(&a).unwrap(),
Origin::from_request(&b).unwrap()
);
}
#[test]
fn dns_and_static_are_distinct_origins() {
let dns = Request::get("https://example.org/").build().unwrap();
let stat = Request::get("https://example.org/")
.target(addr(1))
.build()
.unwrap();
assert_ne!(
Origin::from_request(&dns).unwrap(),
Origin::from_request(&stat).unwrap()
);
}
}