2021-12-16 21:42:37 +00:00
|
|
|
use hyper::{Body, Method, Request};
|
2021-03-17 20:24:28 +00:00
|
|
|
use serde::Deserialize;
|
2021-12-26 20:18:42 +00:00
|
|
|
|
|
|
|
use crate::Error;
|
2016-03-13 20:03:09 +00:00
|
|
|
|
2021-06-10 20:24:40 +00:00
|
|
|
pub type SocketAddress = (String, u16);
|
|
|
|
|
2021-06-21 21:49:37 +00:00
|
|
|
#[derive(Default)]
|
2021-11-26 22:21:27 +00:00
|
|
|
pub struct AccessPoints {
|
2021-06-21 21:49:37 +00:00
|
|
|
accesspoint: Vec<SocketAddress>,
|
|
|
|
dealer: Vec<SocketAddress>,
|
|
|
|
spclient: Vec<SocketAddress>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
2021-11-26 22:21:27 +00:00
|
|
|
pub struct ApResolveData {
|
2021-06-10 20:24:40 +00:00
|
|
|
accesspoint: Vec<String>,
|
|
|
|
dealer: Vec<String>,
|
|
|
|
spclient: Vec<String>,
|
|
|
|
}
|
|
|
|
|
2021-06-21 21:49:37 +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.
|
|
|
|
impl Default for ApResolveData {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
accesspoint: vec![String::from("ap.spotify.com:443")],
|
|
|
|
dealer: vec![String::from("dealer.spotify.com:443")],
|
|
|
|
spclient: vec![String::from("spclient.wg.spotify.com:443")],
|
|
|
|
}
|
|
|
|
}
|
2021-06-10 20:24:40 +00:00
|
|
|
}
|
2018-07-03 11:09:22 +00:00
|
|
|
|
2021-06-21 21:49:37 +00:00
|
|
|
component! {
|
|
|
|
ApResolver : ApResolverInner {
|
|
|
|
data: AccessPoints = AccessPoints::default(),
|
|
|
|
}
|
2021-06-10 20:24:40 +00:00
|
|
|
}
|
|
|
|
|
2021-06-21 21:49:37 +00:00
|
|
|
impl ApResolver {
|
2021-06-22 19:39:38 +00:00
|
|
|
// return a port if a proxy URL and/or a proxy port was specified. This is useful even when
|
|
|
|
// there is no proxy, but firewalls only allow certain ports (e.g. 443 and not 4070).
|
2021-11-26 22:21:27 +00:00
|
|
|
pub fn port_config(&self) -> Option<u16> {
|
2021-06-22 19:39:38 +00:00
|
|
|
if self.session().config().proxy.is_some() || self.session().config().ap_port.is_some() {
|
|
|
|
Some(self.session().config().ap_port.unwrap_or(443))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_data(&self, data: Vec<String>) -> Vec<SocketAddress> {
|
2021-06-21 21:49:37 +00:00
|
|
|
data.into_iter()
|
|
|
|
.filter_map(|ap| {
|
|
|
|
let mut split = ap.rsplitn(2, ':');
|
2021-11-26 22:21:27 +00:00
|
|
|
let port = split.next()?;
|
2021-06-21 21:49:37 +00:00
|
|
|
let host = split.next()?.to_owned();
|
|
|
|
let port: u16 = port.parse().ok()?;
|
2021-06-22 19:39:38 +00:00
|
|
|
if let Some(p) = self.port_config() {
|
|
|
|
if p != port {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
2021-06-21 21:49:37 +00:00
|
|
|
Some((host, port))
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
2021-03-17 20:24:28 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
pub async fn try_apresolve(&self) -> Result<ApResolveData, Error> {
|
2021-06-21 21:49:37 +00:00
|
|
|
let req = Request::builder()
|
2021-12-16 21:42:37 +00:00
|
|
|
.method(Method::GET)
|
2021-06-21 21:49:37 +00:00
|
|
|
.uri("http://apresolve.spotify.com/?type=accesspoint&type=dealer&type=spclient")
|
2021-11-26 22:21:27 +00:00
|
|
|
.body(Body::empty())?;
|
2021-04-02 12:44:42 +00:00
|
|
|
|
2021-06-21 21:49:37 +00:00
|
|
|
let body = self.session().http_client().request_body(req).await?;
|
|
|
|
let data: ApResolveData = serde_json::from_slice(body.as_ref())?;
|
2021-04-02 12:44:42 +00:00
|
|
|
|
2021-06-21 21:49:37 +00:00
|
|
|
Ok(data)
|
|
|
|
}
|
2021-04-02 12:44:42 +00:00
|
|
|
|
2021-06-21 21:49:37 +00:00
|
|
|
async fn apresolve(&self) {
|
|
|
|
let result = self.try_apresolve().await;
|
2021-06-22 19:39:38 +00:00
|
|
|
|
2021-06-21 21:49:37 +00:00
|
|
|
self.lock(|inner| {
|
|
|
|
let data = match result {
|
|
|
|
Ok(data) => data,
|
|
|
|
Err(e) => {
|
|
|
|
warn!("Failed to resolve access points, using fallbacks: {}", e);
|
|
|
|
ApResolveData::default()
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-06-22 19:39:38 +00:00
|
|
|
inner.data.accesspoint = self.process_data(data.accesspoint);
|
|
|
|
inner.data.dealer = self.process_data(data.dealer);
|
|
|
|
inner.data.spclient = self.process_data(data.spclient);
|
2021-06-21 21:49:37 +00:00
|
|
|
})
|
2021-04-02 12:44:42 +00:00
|
|
|
}
|
|
|
|
|
2021-06-21 21:49:37 +00:00
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
self.lock(|inner| {
|
|
|
|
inner.data.accesspoint.is_empty()
|
|
|
|
|| inner.data.dealer.is_empty()
|
|
|
|
|| inner.data.spclient.is_empty()
|
|
|
|
})
|
|
|
|
}
|
2021-04-02 12:44:42 +00:00
|
|
|
|
2022-01-05 19:44:08 +00:00
|
|
|
pub async fn resolve(&self, endpoint: &str) -> Result<SocketAddress, Error> {
|
2021-06-21 21:49:37 +00:00
|
|
|
if self.is_empty() {
|
|
|
|
self.apresolve().await;
|
|
|
|
}
|
|
|
|
|
2021-06-22 19:39:38 +00:00
|
|
|
self.lock(|inner| {
|
|
|
|
let access_point = match endpoint {
|
|
|
|
// take the first position instead of the last with `pop`, because Spotify returns
|
|
|
|
// access points with ports 4070, 443 and 80 in order of preference from highest
|
|
|
|
// to lowest.
|
|
|
|
"accesspoint" => inner.data.accesspoint.remove(0),
|
|
|
|
"dealer" => inner.data.dealer.remove(0),
|
|
|
|
"spclient" => inner.data.spclient.remove(0),
|
2022-01-05 19:44:08 +00:00
|
|
|
_ => {
|
|
|
|
return Err(Error::unimplemented(format!(
|
|
|
|
"No implementation to resolve access point {}",
|
|
|
|
endpoint
|
|
|
|
)))
|
|
|
|
}
|
2021-06-22 19:39:38 +00:00
|
|
|
};
|
2022-01-06 08:43:50 +00:00
|
|
|
|
2022-01-05 19:44:08 +00:00
|
|
|
Ok(access_point)
|
2021-06-21 21:49:37 +00:00
|
|
|
})
|
2021-04-02 12:44:42 +00:00
|
|
|
}
|
|
|
|
}
|