diff --git a/Cargo.lock b/Cargo.lock index a922f3763..dffaaf259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -389,9 +389,9 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "der" @@ -1331,10 +1331,10 @@ dependencies = [ name = "librespot" version = "0.5.0-dev" dependencies = [ + "data-encoding", "env_logger", "futures-util", "getopts", - "hex", "librespot-audio", "librespot-connect", "librespot-core", @@ -1398,13 +1398,13 @@ dependencies = [ "base64 0.21.5", "byteorder", "bytes", + "data-encoding", "dns-sd", "env_logger", "form_urlencoded", "futures-core", "futures-util", "governor", - "hex", "hmac", "http", "httparse", diff --git a/Cargo.toml b/Cargo.toml index d59ec91f3..5b6ade9d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,10 +50,10 @@ path = "protocol" version = "0.5.0-dev" [dependencies] +data-encoding = "2.5" env_logger = { version = "0.10", default-features = false, features = ["color", "humantime", "auto-color"] } futures-util = { version = "0.3", default_features = false } getopts = "0.2" -hex = "0.4" log = "0.4" rpassword = "7.0" sha1 = "0.10" diff --git a/core/Cargo.toml b/core/Cargo.toml index 374ae75b3..c267f48aa 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -23,7 +23,6 @@ form_urlencoded = "1.0" futures-core = "0.3" futures-util = { version = "0.3", features = ["alloc", "bilock", "sink", "unstable"] } governor = { version = "0.6", default-features = false, features = ["std", "jitter"] } -hex = "0.4" hmac = "0.12" httparse = "1.7" http = "0.2" @@ -57,6 +56,7 @@ tokio-tungstenite = { version = "*", default-features = false, features = ["rust tokio-util = { version = "0.7", features = ["codec"] } url = "2" uuid = { version = "1", default-features = false, features = ["fast-rng", "v4"] } +data-encoding = "2.5" [build-dependencies] rand = "0.8" diff --git a/core/src/config.rs b/core/src/config.rs index ada5354b6..bd7136f1c 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -16,17 +16,17 @@ pub struct SessionConfig { pub autoplay: Option, } -impl Default for SessionConfig { - fn default() -> SessionConfig { +impl SessionConfig { + pub(crate) fn default_for_os(os: &str) -> Self { let device_id = uuid::Uuid::new_v4().as_hyphenated().to_string(); - let client_id = match std::env::consts::OS { + let client_id = match os { "android" => ANDROID_CLIENT_ID, "ios" => IOS_CLIENT_ID, _ => KEYMASTER_CLIENT_ID, } .to_owned(); - SessionConfig { + Self { client_id, device_id, proxy: None, @@ -37,6 +37,12 @@ impl Default for SessionConfig { } } +impl Default for SessionConfig { + fn default() -> Self { + Self::default_for_os(std::env::consts::OS) + } +} + #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] pub enum DeviceType { Unknown = 0, diff --git a/core/src/spclient.rs b/core/src/spclient.rs index fbb8ddc54..f4b4ebc18 100644 --- a/core/src/spclient.rs +++ b/core/src/spclient.rs @@ -6,6 +6,7 @@ use std::{ use byteorder::{BigEndian, ByteOrder}; use bytes::Bytes; +use data_encoding::HEXUPPER_PERMISSIVE; use futures_util::future::IntoStream; use http::header::HeaderValue; use hyper::{ @@ -189,9 +190,10 @@ impl SpClient { // on macOS and Windows. On Android and iOS we can send a platform-specific client ID and are // then presented with a hash cash challenge. On Linux, we have to pass the old keymaster ID. // We delegate most of this logic to `SessionConfig`. - let client_id = match OS { + let os = OS; + let client_id = match os { "macos" | "windows" => self.session().client_id(), - _ => SessionConfig::default().client_id, + os => SessionConfig::default_for_os(os).client_id, }; client_data.client_id = client_id; @@ -206,7 +208,7 @@ impl SpClient { let os_version = sys.os_version().unwrap_or_else(|| String::from("0")); let kernel_version = sys.kernel_version().unwrap_or_else(|| String::from("0")); - match OS { + match os { "windows" => { let os_version = os_version.parse::().unwrap_or(10.) as i32; let kernel_version = kernel_version.parse::().unwrap_or(21370); @@ -269,7 +271,10 @@ impl SpClient { match ClientTokenResponseType::from_i32(message.response_type.value()) { // depending on the platform, you're either given a token immediately // or are presented a hash cash challenge to solve first - Some(ClientTokenResponseType::RESPONSE_GRANTED_TOKEN_RESPONSE) => break message, + Some(ClientTokenResponseType::RESPONSE_GRANTED_TOKEN_RESPONSE) => { + debug!("Received a granted token"); + break message; + } Some(ClientTokenResponseType::RESPONSE_CHALLENGES_RESPONSE) => { debug!("Received a hash cash challenge, solving..."); @@ -279,20 +284,22 @@ impl SpClient { let hash_cash_challenge = challenge.evaluate_hashcash_parameters(); let ctx = vec![]; - let prefix = hex::decode(&hash_cash_challenge.prefix).map_err(|e| { - Error::failed_precondition(format!( - "Unable to decode hash cash challenge: {e}" - )) - })?; + let prefix = HEXUPPER_PERMISSIVE + .decode(hash_cash_challenge.prefix.as_bytes()) + .map_err(|e| { + Error::failed_precondition(format!( + "Unable to decode hash cash challenge: {e}" + )) + })?; let length = hash_cash_challenge.length; - let mut suffix = vec![0; 0x10]; + let mut suffix = [0u8; 0x10]; let answer = Self::solve_hash_cash(&ctx, &prefix, length, &mut suffix); match answer { Ok(_) => { // the suffix must be in uppercase - let suffix = hex::encode(suffix).to_uppercase(); + let suffix = HEXUPPER_PERMISSIVE.encode(&suffix); let mut answer_message = ClientTokenRequest::new(); answer_message.request_type = @@ -302,7 +309,7 @@ impl SpClient { let challenge_answers = answer_message.mut_challenge_answers(); let mut challenge_answer = ChallengeAnswer::new(); - challenge_answer.mut_hash_cash().suffix = suffix.to_string(); + challenge_answer.mut_hash_cash().suffix = suffix; challenge_answer.ChallengeType = ChallengeType::CHALLENGE_HASH_CASH.into(); @@ -477,11 +484,14 @@ impl SpClient { HeaderValue::from_str(&format!("{} {}", token.token_type, token.access_token,))?, ); - if let Ok(client_token) = self.client_token().await { - headers_mut.insert(CLIENT_TOKEN, HeaderValue::from_str(&client_token)?); - } else { - // currently these endpoints seem to work fine without it - warn!("Unable to get client token. Trying to continue without..."); + match self.client_token().await { + Ok(client_token) => { + let _ = headers_mut.insert(CLIENT_TOKEN, HeaderValue::from_str(&client_token)?); + } + Err(e) => { + // currently these endpoints seem to work fine without it + warn!("Unable to get client token: {e} Trying to continue without...") + } } last_response = self.session().http_client().request_body(request).await; diff --git a/src/main.rs b/src/main.rs index 11574d925..a00617195 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use data_encoding::HEXLOWER; use futures_util::StreamExt; use log::{debug, error, info, trace, warn}; use sha1::{Digest, Sha1}; @@ -39,7 +40,7 @@ mod player_event_handler; use player_event_handler::{run_program_on_sink_events, EventHandler}; fn device_id(name: &str) -> String { - hex::encode(Sha1::digest(name.as_bytes())) + HEXLOWER.encode(&Sha1::digest(name.as_bytes())) } fn usage(program: &str, opts: &getopts::Options) -> String {