1use std::{
2 collections::HashMap,
3 future::Future,
4 io,
5 pin::Pin,
6 sync::{Arc, Weak},
7 task::{Context, Poll},
8 time::{Duration, SystemTime, UNIX_EPOCH},
9};
10
11use byteorder::{BigEndian, ByteOrder};
12use bytes::Bytes;
13use futures_core::TryStream;
14use futures_util::{future, ready, StreamExt, TryStreamExt};
15use num_traits::FromPrimitive;
16use once_cell::sync::OnceCell;
17use parking_lot::RwLock;
18use quick_xml::events::Event;
19use thiserror::Error;
20use tokio::{sync::mpsc, time::Instant};
21use tokio_stream::wrappers::UnboundedReceiverStream;
22
23use crate::{
24 apresolve::ApResolver,
25 audio_key::AudioKeyManager,
26 authentication::Credentials,
27 cache::Cache,
28 channel::ChannelManager,
29 config::SessionConfig,
30 connection::{self, AuthenticationError},
31 http_client::HttpClient,
32 mercury::MercuryManager,
33 packet::PacketType,
34 protocol::keyexchange::ErrorCode,
35 spclient::SpClient,
36 token::TokenProvider,
37 Error,
38};
39
40#[derive(Debug, Error)]
41pub enum SessionError {
42 #[error(transparent)]
43 AuthenticationError(#[from] AuthenticationError),
44 #[error("Cannot create session: {0}")]
45 IoError(#[from] io::Error),
46 #[error("Session is not connected")]
47 NotConnected,
48 #[error("packet {0} unknown")]
49 Packet(u8),
50}
51
52impl From<SessionError> for Error {
53 fn from(err: SessionError) -> Self {
54 match err {
55 SessionError::AuthenticationError(_) => Error::unauthenticated(err),
56 SessionError::IoError(_) => Error::unavailable(err),
57 SessionError::NotConnected => Error::unavailable(err),
58 SessionError::Packet(_) => Error::unimplemented(err),
59 }
60 }
61}
62
63pub type UserAttributes = HashMap<String, String>;
64
65#[derive(Debug, Clone, Default)]
66pub struct UserData {
67 pub country: String,
68 pub canonical_username: String,
69 pub attributes: UserAttributes,
70}
71
72#[derive(Debug, Clone, Default)]
73struct SessionData {
74 client_id: String,
75 client_name: String,
76 client_brand_name: String,
77 client_model_name: String,
78 connection_id: String,
79 time_delta: i64,
80 invalid: bool,
81 user_data: UserData,
82 last_ping: Option<Instant>,
83}
84
85struct SessionInternal {
86 config: SessionConfig,
87 data: RwLock<SessionData>,
88
89 http_client: HttpClient,
90 tx_connection: OnceCell<mpsc::UnboundedSender<(u8, Vec<u8>)>>,
91
92 apresolver: OnceCell<ApResolver>,
93 audio_key: OnceCell<AudioKeyManager>,
94 channel: OnceCell<ChannelManager>,
95 mercury: OnceCell<MercuryManager>,
96 spclient: OnceCell<SpClient>,
97 token_provider: OnceCell<TokenProvider>,
98 cache: Option<Arc<Cache>>,
99
100 handle: tokio::runtime::Handle,
101}
102
103#[derive(Clone)]
113pub struct Session(Arc<SessionInternal>);
114
115impl Session {
116 pub fn new(config: SessionConfig, cache: Option<Cache>) -> Self {
117 let http_client = HttpClient::new(config.proxy.as_ref());
118
119 debug!("new Session");
120
121 let session_data = SessionData {
122 client_id: config.client_id.clone(),
123 ..SessionData::default()
124 };
125
126 Self(Arc::new(SessionInternal {
127 config,
128 data: RwLock::new(session_data),
129 http_client,
130 tx_connection: OnceCell::new(),
131 cache: cache.map(Arc::new),
132 apresolver: OnceCell::new(),
133 audio_key: OnceCell::new(),
134 channel: OnceCell::new(),
135 mercury: OnceCell::new(),
136 spclient: OnceCell::new(),
137 token_provider: OnceCell::new(),
138 handle: tokio::runtime::Handle::current(),
139 }))
140 }
141
142 pub async fn connect(
143 &self,
144 credentials: Credentials,
145 store_credentials: bool,
146 ) -> Result<(), Error> {
147 let (reusable_credentials, transport) = loop {
148 let ap = self.apresolver().resolve("accesspoint").await?;
149 info!("Connecting to AP \"{}:{}\"", ap.0, ap.1);
150 let mut transport =
151 connection::connect(&ap.0, ap.1, self.config().proxy.as_ref()).await?;
152
153 match connection::authenticate(
154 &mut transport,
155 credentials.clone(),
156 &self.config().device_id,
157 )
158 .await
159 {
160 Ok(creds) => break (creds, transport),
161 Err(e) => {
162 if let Some(AuthenticationError::LoginFailed(ErrorCode::TryAnotherAP)) =
163 e.error.downcast_ref::<AuthenticationError>()
164 {
165 warn!("Instructed to try another access point...");
166 continue;
167 } else {
168 return Err(e);
169 }
170 }
171 }
172 };
173
174 info!("Authenticated as \"{}\" !", reusable_credentials.username);
175 self.set_username(&reusable_credentials.username);
176 if let Some(cache) = self.cache() {
177 if store_credentials {
178 let cred_changed = cache
179 .credentials()
180 .map(|c| c != reusable_credentials)
181 .unwrap_or(true);
182 if cred_changed {
183 cache.save_credentials(&reusable_credentials);
184 }
185 }
186 }
187
188 let (tx_connection, rx_connection) = mpsc::unbounded_channel();
189 self.0
190 .tx_connection
191 .set(tx_connection)
192 .map_err(|_| SessionError::NotConnected)?;
193
194 let (sink, stream) = transport.split();
195 let sender_task = UnboundedReceiverStream::new(rx_connection)
196 .map(Ok)
197 .forward(sink);
198 let receiver_task = DispatchTask(stream, self.weak());
199 let timeout_task = Session::session_timeout(self.weak());
200
201 tokio::spawn(async move {
202 let result = future::try_join3(sender_task, receiver_task, timeout_task).await;
203
204 if let Err(e) = result {
205 error!("{}", e);
206 }
207 });
208
209 Ok(())
210 }
211
212 pub fn apresolver(&self) -> &ApResolver {
213 self.0
214 .apresolver
215 .get_or_init(|| ApResolver::new(self.weak()))
216 }
217
218 pub fn audio_key(&self) -> &AudioKeyManager {
219 self.0
220 .audio_key
221 .get_or_init(|| AudioKeyManager::new(self.weak()))
222 }
223
224 pub fn channel(&self) -> &ChannelManager {
225 self.0
226 .channel
227 .get_or_init(|| ChannelManager::new(self.weak()))
228 }
229
230 pub fn http_client(&self) -> &HttpClient {
231 &self.0.http_client
232 }
233
234 pub fn mercury(&self) -> &MercuryManager {
235 self.0
236 .mercury
237 .get_or_init(|| MercuryManager::new(self.weak()))
238 }
239
240 pub fn spclient(&self) -> &SpClient {
241 self.0.spclient.get_or_init(|| SpClient::new(self.weak()))
242 }
243
244 pub fn token_provider(&self) -> &TokenProvider {
245 self.0
246 .token_provider
247 .get_or_init(|| TokenProvider::new(self.weak()))
248 }
249
250 async fn session_timeout(session: SessionWeak) -> io::Result<()> {
253 const SESSION_TIMEOUT: Duration = Duration::from_secs(125);
255
256 while let Some(session) = session.try_upgrade() {
257 if session.is_invalid() {
258 break;
259 }
260 let last_ping = session.0.data.read().last_ping.unwrap_or_else(Instant::now);
261 if last_ping.elapsed() >= SESSION_TIMEOUT {
262 session.shutdown();
263 return Err(io::Error::new(
265 io::ErrorKind::TimedOut,
266 "session lost connection to server",
267 ));
268 }
269 drop(session);
271 tokio::time::sleep_until(last_ping + SESSION_TIMEOUT).await;
273 }
274 Ok(())
275 }
276
277 pub fn time_delta(&self) -> i64 {
278 self.0.data.read().time_delta
279 }
280
281 pub fn spawn<T>(&self, task: T)
282 where
283 T: Future + Send + 'static,
284 T::Output: Send + 'static,
285 {
286 self.0.handle.spawn(task);
287 }
288
289 fn debug_info(&self) {
290 debug!(
291 "Session strong={} weak={}",
292 Arc::strong_count(&self.0),
293 Arc::weak_count(&self.0)
294 );
295 }
296
297 fn check_catalogue(attributes: &UserAttributes) {
298 if let Some(account_type) = attributes.get("type") {
299 if account_type != "premium" {
300 info!("Please support Spotify and your artists and sign up for a premium account.");
302
303 }
306 }
307 }
308
309 fn dispatch(&self, cmd: u8, data: Bytes) -> Result<(), Error> {
310 use PacketType::*;
311
312 let packet_type = FromPrimitive::from_u8(cmd);
313 let cmd = match packet_type {
314 Some(cmd) => cmd,
315 None => {
316 trace!("Ignoring unknown packet {:x}", cmd);
317 return Err(SessionError::Packet(cmd).into());
318 }
319 };
320
321 match packet_type {
322 Some(Ping) => {
323 let server_timestamp = BigEndian::read_u32(data.as_ref()) as i64;
324 let timestamp = SystemTime::now()
325 .duration_since(UNIX_EPOCH)
326 .unwrap_or(Duration::ZERO)
327 .as_secs() as i64;
328
329 {
330 let mut data = self.0.data.write();
331 data.time_delta = server_timestamp.saturating_sub(timestamp);
332 data.last_ping = Some(Instant::now());
333 }
334
335 self.debug_info();
336 self.send_packet(Pong, vec![0, 0, 0, 0])
337 }
338 Some(CountryCode) => {
339 let country = String::from_utf8(data.as_ref().to_owned())?;
340 info!("Country: {:?}", country);
341 self.0.data.write().user_data.country = country;
342 Ok(())
343 }
344 Some(StreamChunkRes) | Some(ChannelError) => self.channel().dispatch(cmd, data),
345 Some(AesKey) | Some(AesKeyError) => self.audio_key().dispatch(cmd, data),
346 Some(MercuryReq) | Some(MercurySub) | Some(MercuryUnsub) | Some(MercuryEvent) => {
347 self.mercury().dispatch(cmd, data)
348 }
349 Some(ProductInfo) => {
350 let data = std::str::from_utf8(&data)?;
351 let mut reader = quick_xml::Reader::from_str(data);
352
353 let mut buf = Vec::new();
354 let mut current_element = String::new();
355 let mut user_attributes: UserAttributes = HashMap::new();
356
357 loop {
358 match reader.read_event_into(&mut buf) {
359 Ok(Event::Start(ref element)) => {
360 current_element = std::str::from_utf8(element)?.to_owned()
361 }
362 Ok(Event::End(_)) => {
363 current_element = String::new();
364 }
365 Ok(Event::Text(ref value)) => {
366 if !current_element.is_empty() {
367 let _ = user_attributes
368 .insert(current_element.clone(), value.unescape()?.to_string());
369 }
370 }
371 Ok(Event::Eof) => break,
372 Ok(_) => (),
373 Err(e) => warn!(
374 "Error parsing XML at position {}: {:?}",
375 reader.buffer_position(),
376 e
377 ),
378 }
379 }
380
381 trace!("Received product info: {:#?}", user_attributes);
382 Self::check_catalogue(&user_attributes);
383
384 self.0.data.write().user_data.attributes = user_attributes;
385 Ok(())
386 }
387 Some(PongAck)
388 | Some(SecretBlock)
389 | Some(LegacyWelcome)
390 | Some(UnknownDataAllZeros)
391 | Some(LicenseVersion) => Ok(()),
392 _ => {
393 trace!("Ignoring {:?} packet with data {:#?}", cmd, data);
394 Err(SessionError::Packet(cmd as u8).into())
395 }
396 }
397 }
398
399 pub fn send_packet(&self, cmd: PacketType, data: Vec<u8>) -> Result<(), Error> {
400 match self.0.tx_connection.get() {
401 Some(tx) => Ok(tx.send((cmd as u8, data))?),
402 None => Err(SessionError::NotConnected.into()),
403 }
404 }
405
406 pub fn cache(&self) -> Option<&Arc<Cache>> {
407 self.0.cache.as_ref()
408 }
409
410 pub fn config(&self) -> &SessionConfig {
411 &self.0.config
412 }
413
414 pub fn user_data(&self) -> UserData {
418 self.0.data.read().user_data.clone()
419 }
420
421 pub fn device_id(&self) -> &str {
422 &self.config().device_id
423 }
424
425 pub fn client_id(&self) -> String {
426 self.0.data.read().client_id.clone()
427 }
428
429 pub fn set_client_id(&self, client_id: &str) {
430 self.0.data.write().client_id = client_id.to_owned();
431 }
432
433 pub fn client_name(&self) -> String {
434 self.0.data.read().client_name.clone()
435 }
436
437 pub fn set_client_name(&self, client_name: &str) {
438 self.0.data.write().client_name = client_name.to_owned();
439 }
440
441 pub fn client_brand_name(&self) -> String {
442 self.0.data.read().client_brand_name.clone()
443 }
444
445 pub fn set_client_brand_name(&self, client_brand_name: &str) {
446 self.0.data.write().client_brand_name = client_brand_name.to_owned();
447 }
448
449 pub fn client_model_name(&self) -> String {
450 self.0.data.read().client_model_name.clone()
451 }
452
453 pub fn set_client_model_name(&self, client_model_name: &str) {
454 self.0.data.write().client_model_name = client_model_name.to_owned();
455 }
456
457 pub fn connection_id(&self) -> String {
458 self.0.data.read().connection_id.clone()
459 }
460
461 pub fn set_connection_id(&self, connection_id: &str) {
462 self.0.data.write().connection_id = connection_id.to_owned();
463 }
464
465 pub fn username(&self) -> String {
466 self.0.data.read().user_data.canonical_username.clone()
467 }
468
469 pub fn set_username(&self, username: &str) {
470 self.0.data.write().user_data.canonical_username = username.to_owned();
471 }
472
473 pub fn country(&self) -> String {
474 self.0.data.read().user_data.country.clone()
475 }
476
477 pub fn filter_explicit_content(&self) -> bool {
478 match self.get_user_attribute("filter-explicit-content") {
479 Some(value) => matches!(&*value, "1"),
480 None => false,
481 }
482 }
483
484 pub fn autoplay(&self) -> bool {
485 if let Some(overide) = self.config().autoplay {
486 return overide;
487 }
488
489 match self.get_user_attribute("autoplay") {
490 Some(value) => matches!(&*value, "1"),
491 None => false,
492 }
493 }
494
495 pub fn set_user_attribute(&self, key: &str, value: &str) -> Option<String> {
496 let mut dummy_attributes = UserAttributes::new();
497 dummy_attributes.insert(key.to_owned(), value.to_owned());
498 Self::check_catalogue(&dummy_attributes);
499
500 self.0
501 .data
502 .write()
503 .user_data
504 .attributes
505 .insert(key.to_owned(), value.to_owned())
506 }
507
508 pub fn set_user_attributes(&self, attributes: UserAttributes) {
509 Self::check_catalogue(&attributes);
510
511 self.0.data.write().user_data.attributes.extend(attributes)
512 }
513
514 pub fn get_user_attribute(&self, key: &str) -> Option<String> {
515 self.0
516 .data
517 .read()
518 .user_data
519 .attributes
520 .get(key)
521 .map(Clone::clone)
522 }
523
524 fn weak(&self) -> SessionWeak {
525 SessionWeak(Arc::downgrade(&self.0))
526 }
527
528 pub fn shutdown(&self) {
529 debug!("Invalidating session");
530 self.0.data.write().invalid = true;
531 self.mercury().shutdown();
532 self.channel().shutdown();
533 }
534
535 pub fn is_invalid(&self) -> bool {
536 self.0.data.read().invalid
537 }
538}
539
540#[derive(Clone)]
541pub struct SessionWeak(Weak<SessionInternal>);
542
543impl SessionWeak {
544 fn try_upgrade(&self) -> Option<Session> {
545 self.0.upgrade().map(Session)
546 }
547
548 pub(crate) fn upgrade(&self) -> Session {
549 self.try_upgrade()
550 .expect("session was dropped and so should have this component")
551 }
552}
553
554impl Drop for SessionInternal {
555 fn drop(&mut self) {
556 debug!("drop Session");
557 }
558}
559
560struct DispatchTask<S>(S, SessionWeak)
561where
562 S: TryStream<Ok = (u8, Bytes)> + Unpin;
563
564impl<S> Future for DispatchTask<S>
565where
566 S: TryStream<Ok = (u8, Bytes)> + Unpin,
567 <S as TryStream>::Ok: std::fmt::Debug,
568{
569 type Output = Result<(), S::Error>;
570
571 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
572 let session = match self.1.try_upgrade() {
573 Some(session) => session,
574 None => return Poll::Ready(Ok(())),
575 };
576
577 loop {
578 let (cmd, data) = match ready!(self.0.try_poll_next_unpin(cx)) {
579 Some(Ok(t)) => t,
580 None => {
581 warn!("Connection to server closed.");
582 session.shutdown();
583 return Poll::Ready(Ok(()));
584 }
585 Some(Err(e)) => {
586 session.shutdown();
587 return Poll::Ready(Err(e));
588 }
589 };
590
591 if let Err(e) = session.dispatch(cmd, data) {
592 debug!("could not dispatch command: {}", e);
593 }
594 }
595 }
596}
597
598impl<S> Drop for DispatchTask<S>
599where
600 S: TryStream<Ok = (u8, Bytes)> + Unpin,
601{
602 fn drop(&mut self) {
603 debug!("drop Dispatch");
604 }
605}