diff --git a/Cargo.lock b/Cargo.lock index 1f97d578..176655b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,7 +635,7 @@ dependencies = [ "gstreamer-sys", "libc", "muldiv", - "num-rational", + "num-rational 0.3.2", "once_cell", "paste", "pretty-hex", @@ -1223,7 +1223,9 @@ dependencies = [ "hyper-proxy", "librespot-protocol", "log", + "num", "num-bigint", + "num-derive", "num-integer", "num-traits", "once_cell", @@ -1475,6 +1477,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.4.0", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.0" @@ -1487,6 +1503,15 @@ dependencies = [ "rand", ] +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -1508,6 +1533,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.3.2" @@ -1519,6 +1555,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" diff --git a/audio/src/fetch/receive.rs b/audio/src/fetch/receive.rs index 0f056c96..304c0e79 100644 --- a/audio/src/fetch/receive.rs +++ b/audio/src/fetch/receive.rs @@ -7,6 +7,7 @@ use byteorder::{BigEndian, WriteBytesExt}; use bytes::Bytes; use futures_util::StreamExt; use librespot_core::channel::{Channel, ChannelData}; +use librespot_core::packet::PacketType; use librespot_core::session::Session; use librespot_core::spotify_id::FileId; use tempfile::NamedTempFile; @@ -46,7 +47,7 @@ pub fn request_range(session: &Session, file: FileId, offset: usize, length: usi data.write_u32::(start as u32).unwrap(); data.write_u32::(end as u32).unwrap(); - session.send_packet(0x8, data); + session.send_packet(PacketType::StreamChunk, data); channel } diff --git a/core/Cargo.toml b/core/Cargo.toml index 7eb4051c..3c239034 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,7 +26,9 @@ http = "0.2" hyper = { version = "0.14", features = ["client", "tcp", "http1"] } hyper-proxy = { version = "0.9.1", default-features = false } log = "0.4" +num = "0.4" num-bigint = { version = "0.4", features = ["rand"] } +num-derive = "0.3" num-integer = "0.1" num-traits = "0.2" once_cell = "1.5.2" diff --git a/core/src/audio_key.rs b/core/src/audio_key.rs index 3bce1c73..aae268e6 100644 --- a/core/src/audio_key.rs +++ b/core/src/audio_key.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use std::io::Write; use tokio::sync::oneshot; +use crate::packet::PacketType; use crate::spotify_id::{FileId, SpotifyId}; use crate::util::SeqGenerator; @@ -21,19 +22,19 @@ component! { } impl AudioKeyManager { - pub(crate) fn dispatch(&self, cmd: u8, mut data: Bytes) { + pub(crate) fn dispatch(&self, cmd: PacketType, mut data: Bytes) { let seq = BigEndian::read_u32(data.split_to(4).as_ref()); let sender = self.lock(|inner| inner.pending.remove(&seq)); if let Some(sender) = sender { match cmd { - 0xd => { + PacketType::AesKey => { let mut key = [0u8; 16]; key.copy_from_slice(data.as_ref()); let _ = sender.send(Ok(AudioKey(key))); } - 0xe => { + PacketType::AesKeyError => { warn!( "error audio key {:x} {:x}", data.as_ref()[0], @@ -66,6 +67,6 @@ impl AudioKeyManager { data.write_u32::(seq).unwrap(); data.write_u16::(0x0000).unwrap(); - self.session().send_packet(0xc, data) + self.session().send_packet(PacketType::RequestKey, data) } } diff --git a/core/src/channel.rs b/core/src/channel.rs index 4a78a4aa..4461612e 100644 --- a/core/src/channel.rs +++ b/core/src/channel.rs @@ -8,8 +8,10 @@ use bytes::Bytes; use futures_core::Stream; use futures_util::lock::BiLock; use futures_util::{ready, StreamExt}; +use num_traits::FromPrimitive; use tokio::sync::mpsc; +use crate::packet::PacketType; use crate::util::SeqGenerator; component! { @@ -66,7 +68,7 @@ impl ChannelManager { (seq, channel) } - pub(crate) fn dispatch(&self, cmd: u8, mut data: Bytes) { + pub(crate) fn dispatch(&self, cmd: PacketType, mut data: Bytes) { use std::collections::hash_map::Entry; let id: u16 = BigEndian::read_u16(data.split_to(2).as_ref()); @@ -87,7 +89,7 @@ impl ChannelManager { inner.download_measurement_bytes += data.len(); if let Entry::Occupied(entry) = inner.channels.entry(id) { - let _ = entry.get().send((cmd, data)); + let _ = entry.get().send((cmd as u8, data)); } }); } @@ -109,7 +111,8 @@ impl Channel { fn recv_packet(&mut self, cx: &mut Context<'_>) -> Poll> { let (cmd, packet) = ready!(self.receiver.poll_recv(cx)).ok_or(ChannelError)?; - if cmd == 0xa { + let packet_type = FromPrimitive::from_u8(cmd); + if let Some(PacketType::ChannelError) = packet_type { let code = BigEndian::read_u16(&packet.as_ref()[..2]); error!("channel error: {} {}", packet.len(), code); diff --git a/core/src/connection/mod.rs b/core/src/connection/mod.rs index bacdc653..472109e6 100644 --- a/core/src/connection/mod.rs +++ b/core/src/connection/mod.rs @@ -7,6 +7,7 @@ pub use self::handshake::handshake; use std::io::{self, ErrorKind}; use futures_util::{SinkExt, StreamExt}; +use num_traits::FromPrimitive; use protobuf::{self, Message, ProtobufError}; use thiserror::Error; use tokio::net::TcpStream; @@ -14,6 +15,7 @@ use tokio_util::codec::Framed; use url::Url; use crate::authentication::Credentials; +use crate::packet::PacketType; use crate::protocol::keyexchange::{APLoginFailed, ErrorCode}; use crate::version; @@ -95,13 +97,14 @@ pub async fn authenticate( .set_device_id(device_id.to_string()); packet.set_version_string(version::VERSION_STRING.to_string()); - let cmd = 0xab; + let cmd = PacketType::Login; let data = packet.write_to_bytes().unwrap(); - transport.send((cmd, data)).await?; + transport.send((cmd as u8, data)).await?; let (cmd, data) = transport.next().await.expect("EOF")?; - match cmd { - 0xac => { + let packet_type = FromPrimitive::from_u8(cmd); + match packet_type { + Some(PacketType::APWelcome) => { let welcome_data = APWelcome::parse_from_bytes(data.as_ref())?; let reusable_credentials = Credentials { @@ -112,7 +115,7 @@ pub async fn authenticate( Ok(reusable_credentials) } - 0xad => { + Some(PacketType::AuthFailure) => { let error_data = APLoginFailed::parse_from_bytes(data.as_ref())?; Err(error_data.into()) } diff --git a/core/src/lib.rs b/core/src/lib.rs index 32ee0013..b0996993 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,6 +2,7 @@ #[macro_use] extern crate log; +extern crate num_derive; use librespot_protocol as protocol; @@ -21,6 +22,7 @@ mod dealer; pub mod diffie_hellman; mod http_client; pub mod mercury; +pub mod packet; mod proxytunnel; pub mod session; mod socket; diff --git a/core/src/mercury/mod.rs b/core/src/mercury/mod.rs index 57650087..6cf3519e 100644 --- a/core/src/mercury/mod.rs +++ b/core/src/mercury/mod.rs @@ -11,6 +11,7 @@ use futures_util::FutureExt; use protobuf::Message; use tokio::sync::{mpsc, oneshot}; +use crate::packet::PacketType; use crate::protocol; use crate::util::SeqGenerator; @@ -143,7 +144,7 @@ impl MercuryManager { } } - pub(crate) fn dispatch(&self, cmd: u8, mut data: Bytes) { + pub(crate) fn dispatch(&self, cmd: PacketType, mut data: Bytes) { let seq_len = BigEndian::read_u16(data.split_to(2).as_ref()) as usize; let seq = data.split_to(seq_len).as_ref().to_owned(); @@ -154,14 +155,17 @@ impl MercuryManager { let mut pending = match pending { Some(pending) => pending, - None if cmd == 0xb5 => MercuryPending { - parts: Vec::new(), - partial: None, - callback: None, - }, None => { - warn!("Ignore seq {:?} cmd {:x}", seq, cmd); - return; + if let PacketType::MercuryEvent = cmd { + MercuryPending { + parts: Vec::new(), + partial: None, + callback: None, + } + } else { + warn!("Ignore seq {:?} cmd {:x}", seq, cmd as u8); + return; + } } }; @@ -191,7 +195,7 @@ impl MercuryManager { data.split_to(size).as_ref().to_owned() } - fn complete_request(&self, cmd: u8, mut pending: MercuryPending) { + fn complete_request(&self, cmd: PacketType, mut pending: MercuryPending) { let header_data = pending.parts.remove(0); let header = protocol::mercury::Header::parse_from_bytes(&header_data).unwrap(); @@ -208,7 +212,7 @@ impl MercuryManager { if let Some(cb) = pending.callback { let _ = cb.send(Err(MercuryError)); } - } else if cmd == 0xb5 { + } else if let PacketType::MercuryEvent = cmd { self.lock(|inner| { let mut found = false; diff --git a/core/src/mercury/types.rs b/core/src/mercury/types.rs index 402a954c..616225db 100644 --- a/core/src/mercury/types.rs +++ b/core/src/mercury/types.rs @@ -2,6 +2,7 @@ use byteorder::{BigEndian, WriteBytesExt}; use protobuf::Message; use std::io::Write; +use crate::packet::PacketType; use crate::protocol; #[derive(Debug, PartialEq, Eq)] @@ -43,11 +44,12 @@ impl ToString for MercuryMethod { } impl MercuryMethod { - pub fn command(&self) -> u8 { + pub fn command(&self) -> PacketType { + use PacketType::*; match *self { - MercuryMethod::Get | MercuryMethod::Send => 0xb2, - MercuryMethod::Sub => 0xb3, - MercuryMethod::Unsub => 0xb4, + MercuryMethod::Get | MercuryMethod::Send => MercuryReq, + MercuryMethod::Sub => MercurySub, + MercuryMethod::Unsub => MercuryUnsub, } } } diff --git a/core/src/packet.rs b/core/src/packet.rs new file mode 100644 index 00000000..81645145 --- /dev/null +++ b/core/src/packet.rs @@ -0,0 +1,37 @@ +// Ported from librespot-java. Relicensed under MIT with permission. + +use num_derive::{FromPrimitive, ToPrimitive}; + +#[derive(Debug, FromPrimitive, ToPrimitive)] +pub enum PacketType { + SecretBlock = 0x02, + Ping = 0x04, + StreamChunk = 0x08, + StreamChunkRes = 0x09, + ChannelError = 0x0a, + ChannelAbort = 0x0b, + RequestKey = 0x0c, + AesKey = 0x0d, + AesKeyError = 0x0e, + Image = 0x19, + CountryCode = 0x1b, + Pong = 0x49, + PongAck = 0x4a, + Pause = 0x4b, + ProductInfo = 0x50, + LegacyWelcome = 0x69, + LicenseVersion = 0x76, + Login = 0xab, + APWelcome = 0xac, + AuthFailure = 0xad, + MercuryReq = 0xb2, + MercurySub = 0xb3, + MercuryUnsub = 0xb4, + MercuryEvent = 0xb5, + TrackEndedTime = 0x82, + UnknownDataAllZeros = 0x1f, + PreferredLocale = 0x74, + Unknown0x4f = 0x4f, + Unknown0x0f = 0x0f, + Unknown0x10 = 0x10, +} diff --git a/core/src/session.rs b/core/src/session.rs index 2f6e5703..1bf62aa2 100644 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -11,6 +11,7 @@ use byteorder::{BigEndian, ByteOrder}; use bytes::Bytes; use futures_core::TryStream; use futures_util::{future, ready, StreamExt, TryStreamExt}; +use num_traits::FromPrimitive; use once_cell::sync::OnceCell; use thiserror::Error; use tokio::sync::mpsc; @@ -25,6 +26,7 @@ use crate::config::SessionConfig; use crate::connection::{self, AuthenticationError}; use crate::http_client::HttpClient; use crate::mercury::MercuryManager; +use crate::packet::PacketType; use crate::token::TokenProvider; #[derive(Debug, Error)] @@ -184,11 +186,11 @@ impl Session { ); } - #[allow(clippy::match_same_arms)] fn dispatch(&self, cmd: u8, data: Bytes) { - match cmd { - // TODO: add command types - 0x4 => { + use PacketType::*; + let packet_type = FromPrimitive::from_u8(cmd); + match packet_type { + Some(Ping) => { let server_timestamp = BigEndian::read_u32(data.as_ref()) as i64; let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(dur) => dur, @@ -199,24 +201,36 @@ impl Session { self.0.data.write().unwrap().time_delta = server_timestamp - timestamp; self.debug_info(); - self.send_packet(0x49, vec![0, 0, 0, 0]); + self.send_packet(Pong, vec![0, 0, 0, 0]); } - 0x4a => (), - 0x1b => { + Some(PongAck) => {} + Some(CountryCode) => { let country = String::from_utf8(data.as_ref().to_owned()).unwrap(); info!("Country: {:?}", country); self.0.data.write().unwrap().country = country; } - 0x9 | 0xa => self.channel().dispatch(cmd, data), - 0xd | 0xe => self.audio_key().dispatch(cmd, data), - 0xb2..=0xb6 => self.mercury().dispatch(cmd, data), - _ => (), + Some(StreamChunkRes) | Some(ChannelError) => { + self.channel().dispatch(packet_type.unwrap(), data) + } + Some(AesKey) | Some(AesKeyError) => { + self.audio_key().dispatch(packet_type.unwrap(), data) + } + Some(MercuryReq) | Some(MercurySub) | Some(MercuryUnsub) | Some(MercuryEvent) => { + self.mercury().dispatch(packet_type.unwrap(), data) + } + _ => { + if let Some(packet_type) = PacketType::from_u8(cmd) { + trace!("Ignoring {:?} packet", packet_type); + } else { + trace!("Ignoring unknown packet {:x}", cmd); + } + } } } - pub fn send_packet(&self, cmd: u8, data: Vec) { - self.0.tx_connection.send((cmd, data)).unwrap(); + pub fn send_packet(&self, cmd: PacketType, data: Vec) { + self.0.tx_connection.send((cmd as u8, data)).unwrap(); } pub fn cache(&self) -> Option<&Arc> { diff --git a/metadata/src/cover.rs b/metadata/src/cover.rs index 408e658e..b483f454 100644 --- a/metadata/src/cover.rs +++ b/metadata/src/cover.rs @@ -2,6 +2,7 @@ use byteorder::{BigEndian, WriteBytesExt}; use std::io::Write; use librespot_core::channel::ChannelData; +use librespot_core::packet::PacketType; use librespot_core::session::Session; use librespot_core::spotify_id::FileId; @@ -13,7 +14,7 @@ pub fn get(session: &Session, file: FileId) -> ChannelData { packet.write_u16::(channel_id).unwrap(); packet.write_u16::(0).unwrap(); packet.write(&file.0).unwrap(); - session.send_packet(0x19, packet); + session.send_packet(PacketType::Image, packet); data }