-
Notifications
You must be signed in to change notification settings - Fork 593
/
apresolve.rs
153 lines (129 loc) · 5.09 KB
/
apresolve.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use std::collections::VecDeque;
use bytes::Bytes;
use hyper::{Method, Request};
use serde::Deserialize;
use crate::Error;
pub type SocketAddress = (String, u16);
#[derive(Default)]
pub struct AccessPoints {
accesspoint: VecDeque<SocketAddress>,
dealer: VecDeque<SocketAddress>,
spclient: VecDeque<SocketAddress>,
}
#[derive(Deserialize, Default)]
pub struct ApResolveData {
accesspoint: Vec<String>,
dealer: Vec<String>,
spclient: Vec<String>,
}
impl ApResolveData {
// 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.
fn fallback() -> 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")],
}
}
}
impl AccessPoints {
fn is_any_empty(&self) -> bool {
self.accesspoint.is_empty() || self.dealer.is_empty() || self.spclient.is_empty()
}
}
component! {
ApResolver : ApResolverInner {
data: AccessPoints = AccessPoints::default(),
}
}
impl ApResolver {
// 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).
pub fn port_config(&self) -> Option<u16> {
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_ap_strings(&self, data: Vec<String>) -> VecDeque<SocketAddress> {
let filter_port = self.port_config();
data.into_iter()
.filter_map(|ap| {
let mut split = ap.rsplitn(2, ':');
let port = split.next()?;
let port: u16 = port.parse().ok()?;
let host = split.next()?.to_owned();
match filter_port {
Some(filter_port) if filter_port != port => None,
_ => Some((host, port)),
}
})
.collect()
}
fn parse_resolve_to_access_points(&self, resolve: ApResolveData) -> AccessPoints {
AccessPoints {
accesspoint: self.process_ap_strings(resolve.accesspoint),
dealer: self.process_ap_strings(resolve.dealer),
spclient: self.process_ap_strings(resolve.spclient),
}
}
pub async fn try_apresolve(&self) -> Result<ApResolveData, Error> {
let req = Request::builder()
.method(Method::GET)
.uri("https://apresolve.spotify.com/?type=accesspoint&type=dealer&type=spclient")
.body(Bytes::new())?;
let body = self.session().http_client().request_body(req).await?;
let data: ApResolveData = serde_json::from_slice(body.as_ref())?;
Ok(data)
}
async fn apresolve(&self) {
let result = self.try_apresolve().await;
self.lock(|inner| {
let (data, error) = match result {
Ok(data) => (data, None),
Err(e) => (ApResolveData::default(), Some(e)),
};
inner.data = self.parse_resolve_to_access_points(data);
if inner.data.is_any_empty() {
warn!("Failed to resolve all access points, using fallbacks");
if let Some(error) = error {
warn!("Resolve access points error: {}", error);
}
let fallback = self.parse_resolve_to_access_points(ApResolveData::fallback());
inner.data.accesspoint.extend(fallback.accesspoint);
inner.data.dealer.extend(fallback.dealer);
inner.data.spclient.extend(fallback.spclient);
}
})
}
fn is_any_empty(&self) -> bool {
self.lock(|inner| inner.data.is_any_empty())
}
pub async fn resolve(&self, endpoint: &str) -> Result<SocketAddress, Error> {
if self.is_any_empty() {
self.apresolve().await;
}
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.pop_front(),
"dealer" => inner.data.dealer.pop_front(),
"spclient" => inner.data.spclient.pop_front(),
_ => {
return Err(Error::unimplemented(format!(
"No implementation to resolve access point {endpoint}"
)))
}
};
let access_point = access_point.ok_or_else(|| {
Error::unavailable(format!("No access point available for endpoint {endpoint}"))
})?;
Ok(access_point)
})
}
}