2021-06-20 21:09:27 +00:00
|
|
|
use crate::http_client::HttpClient;
|
|
|
|
use hyper::{Body, Request};
|
2021-03-17 20:24:28 +00:00
|
|
|
use serde::Deserialize;
|
2021-06-20 21:09:27 +00:00
|
|
|
use std::error::Error;
|
2016-03-13 20:03:09 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
const APRESOLVE_ENDPOINT: &str =
|
|
|
|
"http://apresolve.spotify.com/?type=accesspoint&type=dealer&type=spclient";
|
2021-01-30 13:45:31 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
// These addresses probably do some geo-location based traffic management or at least DNS-based
|
|
|
|
// load balancing. They are known to fail when the normal resolvers are up, so that's why they
|
|
|
|
// should only be used as fallback.
|
|
|
|
const AP_FALLBACK: &str = "ap.spotify.com";
|
|
|
|
const DEALER_FALLBACK: &str = "dealer.spotify.com";
|
|
|
|
const SPCLIENT_FALLBACK: &str = "spclient.wg.spotify.com";
|
2016-03-13 20:03:09 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
const FALLBACK_PORT: u16 = 443;
|
|
|
|
|
|
|
|
pub type SocketAddress = (String, u16);
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize)]
|
2021-03-31 18:05:32 +00:00
|
|
|
struct ApResolveData {
|
2021-06-10 20:24:40 +00:00
|
|
|
accesspoint: Vec<String>,
|
|
|
|
dealer: Vec<String>,
|
|
|
|
spclient: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
pub struct AccessPoints {
|
|
|
|
pub accesspoint: SocketAddress,
|
|
|
|
pub dealer: SocketAddress,
|
|
|
|
pub spclient: SocketAddress,
|
2021-03-17 20:24:28 +00:00
|
|
|
}
|
2017-01-18 15:17:10 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
fn select_ap(data: Vec<String>, fallback: &str, ap_port: Option<u16>) -> SocketAddress {
|
|
|
|
let port = ap_port.unwrap_or(FALLBACK_PORT);
|
|
|
|
|
|
|
|
let mut aps = data.into_iter().filter_map(|ap| {
|
|
|
|
let mut split = ap.rsplitn(2, ':');
|
|
|
|
let port = split
|
|
|
|
.next()
|
|
|
|
.expect("rsplitn should not return empty iterator");
|
|
|
|
let host = split.next()?.to_owned();
|
|
|
|
let port: u16 = port.parse().ok()?;
|
|
|
|
Some((host, port))
|
|
|
|
});
|
|
|
|
|
|
|
|
let ap = if ap_port.is_some() {
|
|
|
|
aps.find(|(_, p)| *p == port)
|
|
|
|
} else {
|
|
|
|
aps.next()
|
|
|
|
};
|
|
|
|
|
|
|
|
ap.unwrap_or_else(|| (String::from(fallback), port))
|
|
|
|
}
|
2018-07-03 11:09:22 +00:00
|
|
|
|
2021-06-20 21:09:27 +00:00
|
|
|
async fn try_apresolve(http_client: &HttpClient) -> Result<ApResolveData, Box<dyn Error>> {
|
2021-06-10 20:24:40 +00:00
|
|
|
let req = Request::builder()
|
|
|
|
.method("GET")
|
|
|
|
.uri(APRESOLVE_ENDPOINT)
|
|
|
|
.body(Body::empty())
|
|
|
|
.unwrap();
|
2021-02-10 21:50:08 +00:00
|
|
|
|
2021-06-20 21:09:27 +00:00
|
|
|
let body = http_client.request_body(req).await?;
|
2021-03-31 18:05:32 +00:00
|
|
|
let data: ApResolveData = serde_json::from_slice(body.as_ref())?;
|
2021-02-10 21:50:08 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
Ok(data)
|
|
|
|
}
|
|
|
|
|
2021-06-20 21:09:27 +00:00
|
|
|
pub async fn apresolve(http_client: &HttpClient, ap_port: Option<u16>) -> AccessPoints {
|
|
|
|
let data = try_apresolve(http_client).await.unwrap_or_else(|e| {
|
2021-06-10 20:24:40 +00:00
|
|
|
warn!("Failed to resolve access points: {}, using fallbacks.", e);
|
|
|
|
ApResolveData::default()
|
2021-05-22 17:05:13 +00:00
|
|
|
});
|
2021-03-17 20:24:28 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
let accesspoint = select_ap(data.accesspoint, AP_FALLBACK, ap_port);
|
|
|
|
let dealer = select_ap(data.dealer, DEALER_FALLBACK, ap_port);
|
|
|
|
let spclient = select_ap(data.spclient, SPCLIENT_FALLBACK, ap_port);
|
2021-03-17 20:24:28 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
AccessPoints {
|
|
|
|
accesspoint,
|
|
|
|
dealer,
|
|
|
|
spclient,
|
|
|
|
}
|
2016-03-13 20:03:09 +00:00
|
|
|
}
|
2021-04-02 12:44:42 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use std::net::ToSocketAddrs;
|
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
use super::apresolve;
|
2021-06-20 21:09:27 +00:00
|
|
|
use crate::http_client::HttpClient;
|
2021-04-02 12:44:42 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_apresolve() {
|
2021-06-20 21:09:27 +00:00
|
|
|
let http_client = HttpClient::new(None);
|
|
|
|
let aps = apresolve(&http_client, None).await;
|
2021-04-02 12:44:42 +00:00
|
|
|
|
|
|
|
// Assert that the result contains a valid host and port
|
2021-06-10 20:24:40 +00:00
|
|
|
aps.accesspoint.to_socket_addrs().unwrap().next().unwrap();
|
|
|
|
aps.dealer.to_socket_addrs().unwrap().next().unwrap();
|
|
|
|
aps.spclient.to_socket_addrs().unwrap().next().unwrap();
|
2021-04-02 12:44:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_apresolve_port_443() {
|
2021-06-20 21:09:27 +00:00
|
|
|
let http_client = HttpClient::new(None);
|
|
|
|
let aps = apresolve(&http_client, Some(443)).await;
|
2021-04-02 12:44:42 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
let port = aps
|
|
|
|
.accesspoint
|
|
|
|
.to_socket_addrs()
|
|
|
|
.unwrap()
|
|
|
|
.next()
|
|
|
|
.unwrap()
|
|
|
|
.port();
|
2021-04-02 12:44:42 +00:00
|
|
|
assert_eq!(port, 443);
|
|
|
|
}
|
|
|
|
}
|