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