Use PacketType instead of hex identifiers

This commit is contained in:
Roderick van Domburg 2021-06-22 23:57:38 +02:00
parent 4fe1183a80
commit 0703630041
No known key found for this signature in database
GPG key ID: 7076AA781B43EFE6
12 changed files with 160 additions and 42 deletions

50
Cargo.lock generated
View file

@ -635,7 +635,7 @@ dependencies = [
"gstreamer-sys", "gstreamer-sys",
"libc", "libc",
"muldiv", "muldiv",
"num-rational", "num-rational 0.3.2",
"once_cell", "once_cell",
"paste", "paste",
"pretty-hex", "pretty-hex",
@ -1223,7 +1223,9 @@ dependencies = [
"hyper-proxy", "hyper-proxy",
"librespot-protocol", "librespot-protocol",
"log", "log",
"num",
"num-bigint", "num-bigint",
"num-derive",
"num-integer", "num-integer",
"num-traits", "num-traits",
"once_cell", "once_cell",
@ -1475,6 +1477,20 @@ dependencies = [
"winapi", "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]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.0" version = "0.4.0"
@ -1487,6 +1503,15 @@ dependencies = [
"rand", "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]] [[package]]
name = "num-derive" name = "num-derive"
version = "0.3.3" version = "0.3.3"
@ -1508,6 +1533,17 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "num-rational" name = "num-rational"
version = "0.3.2" version = "0.3.2"
@ -1519,6 +1555,18 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"

View file

@ -7,6 +7,7 @@ use byteorder::{BigEndian, WriteBytesExt};
use bytes::Bytes; use bytes::Bytes;
use futures_util::StreamExt; use futures_util::StreamExt;
use librespot_core::channel::{Channel, ChannelData}; use librespot_core::channel::{Channel, ChannelData};
use librespot_core::packet::PacketType;
use librespot_core::session::Session; use librespot_core::session::Session;
use librespot_core::spotify_id::FileId; use librespot_core::spotify_id::FileId;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
@ -46,7 +47,7 @@ pub fn request_range(session: &Session, file: FileId, offset: usize, length: usi
data.write_u32::<BigEndian>(start as u32).unwrap(); data.write_u32::<BigEndian>(start as u32).unwrap();
data.write_u32::<BigEndian>(end as u32).unwrap(); data.write_u32::<BigEndian>(end as u32).unwrap();
session.send_packet(0x8, data); session.send_packet(PacketType::StreamChunk, data);
channel channel
} }

View file

@ -26,7 +26,9 @@ http = "0.2"
hyper = { version = "0.14", features = ["client", "tcp", "http1"] } hyper = { version = "0.14", features = ["client", "tcp", "http1"] }
hyper-proxy = { version = "0.9.1", default-features = false } hyper-proxy = { version = "0.9.1", default-features = false }
log = "0.4" log = "0.4"
num = "0.4"
num-bigint = { version = "0.4", features = ["rand"] } num-bigint = { version = "0.4", features = ["rand"] }
num-derive = "0.3"
num-integer = "0.1" num-integer = "0.1"
num-traits = "0.2" num-traits = "0.2"
once_cell = "1.5.2" once_cell = "1.5.2"

View file

@ -4,6 +4,7 @@ use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use crate::packet::PacketType;
use crate::spotify_id::{FileId, SpotifyId}; use crate::spotify_id::{FileId, SpotifyId};
use crate::util::SeqGenerator; use crate::util::SeqGenerator;
@ -21,19 +22,19 @@ component! {
} }
impl AudioKeyManager { 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 seq = BigEndian::read_u32(data.split_to(4).as_ref());
let sender = self.lock(|inner| inner.pending.remove(&seq)); let sender = self.lock(|inner| inner.pending.remove(&seq));
if let Some(sender) = sender { if let Some(sender) = sender {
match cmd { match cmd {
0xd => { PacketType::AesKey => {
let mut key = [0u8; 16]; let mut key = [0u8; 16];
key.copy_from_slice(data.as_ref()); key.copy_from_slice(data.as_ref());
let _ = sender.send(Ok(AudioKey(key))); let _ = sender.send(Ok(AudioKey(key)));
} }
0xe => { PacketType::AesKeyError => {
warn!( warn!(
"error audio key {:x} {:x}", "error audio key {:x} {:x}",
data.as_ref()[0], data.as_ref()[0],
@ -66,6 +67,6 @@ impl AudioKeyManager {
data.write_u32::<BigEndian>(seq).unwrap(); data.write_u32::<BigEndian>(seq).unwrap();
data.write_u16::<BigEndian>(0x0000).unwrap(); data.write_u16::<BigEndian>(0x0000).unwrap();
self.session().send_packet(0xc, data) self.session().send_packet(PacketType::RequestKey, data)
} }
} }

View file

@ -8,8 +8,10 @@ use bytes::Bytes;
use futures_core::Stream; use futures_core::Stream;
use futures_util::lock::BiLock; use futures_util::lock::BiLock;
use futures_util::{ready, StreamExt}; use futures_util::{ready, StreamExt};
use num_traits::FromPrimitive;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::packet::PacketType;
use crate::util::SeqGenerator; use crate::util::SeqGenerator;
component! { component! {
@ -66,7 +68,7 @@ impl ChannelManager {
(seq, channel) (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; use std::collections::hash_map::Entry;
let id: u16 = BigEndian::read_u16(data.split_to(2).as_ref()); let id: u16 = BigEndian::read_u16(data.split_to(2).as_ref());
@ -87,7 +89,7 @@ impl ChannelManager {
inner.download_measurement_bytes += data.len(); inner.download_measurement_bytes += data.len();
if let Entry::Occupied(entry) = inner.channels.entry(id) { 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<Result<Bytes, ChannelError>> { fn recv_packet(&mut self, cx: &mut Context<'_>) -> Poll<Result<Bytes, ChannelError>> {
let (cmd, packet) = ready!(self.receiver.poll_recv(cx)).ok_or(ChannelError)?; 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]); let code = BigEndian::read_u16(&packet.as_ref()[..2]);
error!("channel error: {} {}", packet.len(), code); error!("channel error: {} {}", packet.len(), code);

View file

@ -7,6 +7,7 @@ pub use self::handshake::handshake;
use std::io::{self, ErrorKind}; use std::io::{self, ErrorKind};
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
use num_traits::FromPrimitive;
use protobuf::{self, Message, ProtobufError}; use protobuf::{self, Message, ProtobufError};
use thiserror::Error; use thiserror::Error;
use tokio::net::TcpStream; use tokio::net::TcpStream;
@ -14,6 +15,7 @@ use tokio_util::codec::Framed;
use url::Url; use url::Url;
use crate::authentication::Credentials; use crate::authentication::Credentials;
use crate::packet::PacketType;
use crate::protocol::keyexchange::{APLoginFailed, ErrorCode}; use crate::protocol::keyexchange::{APLoginFailed, ErrorCode};
use crate::version; use crate::version;
@ -95,13 +97,14 @@ pub async fn authenticate(
.set_device_id(device_id.to_string()); .set_device_id(device_id.to_string());
packet.set_version_string(version::VERSION_STRING.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(); 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")?; let (cmd, data) = transport.next().await.expect("EOF")?;
match cmd { let packet_type = FromPrimitive::from_u8(cmd);
0xac => { match packet_type {
Some(PacketType::APWelcome) => {
let welcome_data = APWelcome::parse_from_bytes(data.as_ref())?; let welcome_data = APWelcome::parse_from_bytes(data.as_ref())?;
let reusable_credentials = Credentials { let reusable_credentials = Credentials {
@ -112,7 +115,7 @@ pub async fn authenticate(
Ok(reusable_credentials) Ok(reusable_credentials)
} }
0xad => { Some(PacketType::AuthFailure) => {
let error_data = APLoginFailed::parse_from_bytes(data.as_ref())?; let error_data = APLoginFailed::parse_from_bytes(data.as_ref())?;
Err(error_data.into()) Err(error_data.into())
} }

View file

@ -2,6 +2,7 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate num_derive;
use librespot_protocol as protocol; use librespot_protocol as protocol;
@ -21,6 +22,7 @@ mod dealer;
pub mod diffie_hellman; pub mod diffie_hellman;
mod http_client; mod http_client;
pub mod mercury; pub mod mercury;
pub mod packet;
mod proxytunnel; mod proxytunnel;
pub mod session; pub mod session;
mod socket; mod socket;

View file

@ -11,6 +11,7 @@ use futures_util::FutureExt;
use protobuf::Message; use protobuf::Message;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use crate::packet::PacketType;
use crate::protocol; use crate::protocol;
use crate::util::SeqGenerator; 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_len = BigEndian::read_u16(data.split_to(2).as_ref()) as usize;
let seq = data.split_to(seq_len).as_ref().to_owned(); let seq = data.split_to(seq_len).as_ref().to_owned();
@ -154,15 +155,18 @@ impl MercuryManager {
let mut pending = match pending { let mut pending = match pending {
Some(pending) => pending, Some(pending) => pending,
None if cmd == 0xb5 => MercuryPending { None => {
if let PacketType::MercuryEvent = cmd {
MercuryPending {
parts: Vec::new(), parts: Vec::new(),
partial: None, partial: None,
callback: None, callback: None,
}, }
None => { } else {
warn!("Ignore seq {:?} cmd {:x}", seq, cmd); warn!("Ignore seq {:?} cmd {:x}", seq, cmd as u8);
return; return;
} }
}
}; };
for i in 0..count { for i in 0..count {
@ -191,7 +195,7 @@ impl MercuryManager {
data.split_to(size).as_ref().to_owned() 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_data = pending.parts.remove(0);
let header = protocol::mercury::Header::parse_from_bytes(&header_data).unwrap(); let header = protocol::mercury::Header::parse_from_bytes(&header_data).unwrap();
@ -208,7 +212,7 @@ impl MercuryManager {
if let Some(cb) = pending.callback { if let Some(cb) = pending.callback {
let _ = cb.send(Err(MercuryError)); let _ = cb.send(Err(MercuryError));
} }
} else if cmd == 0xb5 { } else if let PacketType::MercuryEvent = cmd {
self.lock(|inner| { self.lock(|inner| {
let mut found = false; let mut found = false;

View file

@ -2,6 +2,7 @@ use byteorder::{BigEndian, WriteBytesExt};
use protobuf::Message; use protobuf::Message;
use std::io::Write; use std::io::Write;
use crate::packet::PacketType;
use crate::protocol; use crate::protocol;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@ -43,11 +44,12 @@ impl ToString for MercuryMethod {
} }
impl MercuryMethod { impl MercuryMethod {
pub fn command(&self) -> u8 { pub fn command(&self) -> PacketType {
use PacketType::*;
match *self { match *self {
MercuryMethod::Get | MercuryMethod::Send => 0xb2, MercuryMethod::Get | MercuryMethod::Send => MercuryReq,
MercuryMethod::Sub => 0xb3, MercuryMethod::Sub => MercurySub,
MercuryMethod::Unsub => 0xb4, MercuryMethod::Unsub => MercuryUnsub,
} }
} }
} }

37
core/src/packet.rs Normal file
View file

@ -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,
}

View file

@ -11,6 +11,7 @@ use byteorder::{BigEndian, ByteOrder};
use bytes::Bytes; use bytes::Bytes;
use futures_core::TryStream; use futures_core::TryStream;
use futures_util::{future, ready, StreamExt, TryStreamExt}; use futures_util::{future, ready, StreamExt, TryStreamExt};
use num_traits::FromPrimitive;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use thiserror::Error; use thiserror::Error;
use tokio::sync::mpsc; use tokio::sync::mpsc;
@ -25,6 +26,7 @@ use crate::config::SessionConfig;
use crate::connection::{self, AuthenticationError}; use crate::connection::{self, AuthenticationError};
use crate::http_client::HttpClient; use crate::http_client::HttpClient;
use crate::mercury::MercuryManager; use crate::mercury::MercuryManager;
use crate::packet::PacketType;
use crate::token::TokenProvider; use crate::token::TokenProvider;
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -184,11 +186,11 @@ impl Session {
); );
} }
#[allow(clippy::match_same_arms)]
fn dispatch(&self, cmd: u8, data: Bytes) { fn dispatch(&self, cmd: u8, data: Bytes) {
match cmd { use PacketType::*;
// TODO: add command types let packet_type = FromPrimitive::from_u8(cmd);
0x4 => { match packet_type {
Some(Ping) => {
let server_timestamp = BigEndian::read_u32(data.as_ref()) as i64; let server_timestamp = BigEndian::read_u32(data.as_ref()) as i64;
let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) { let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(dur) => dur, Ok(dur) => dur,
@ -199,24 +201,36 @@ impl Session {
self.0.data.write().unwrap().time_delta = server_timestamp - timestamp; self.0.data.write().unwrap().time_delta = server_timestamp - timestamp;
self.debug_info(); self.debug_info();
self.send_packet(0x49, vec![0, 0, 0, 0]); self.send_packet(Pong, vec![0, 0, 0, 0]);
} }
0x4a => (), Some(PongAck) => {}
0x1b => { Some(CountryCode) => {
let country = String::from_utf8(data.as_ref().to_owned()).unwrap(); let country = String::from_utf8(data.as_ref().to_owned()).unwrap();
info!("Country: {:?}", country); info!("Country: {:?}", country);
self.0.data.write().unwrap().country = country; self.0.data.write().unwrap().country = country;
} }
0x9 | 0xa => self.channel().dispatch(cmd, data), Some(StreamChunkRes) | Some(ChannelError) => {
0xd | 0xe => self.audio_key().dispatch(cmd, data), self.channel().dispatch(packet_type.unwrap(), data)
0xb2..=0xb6 => self.mercury().dispatch(cmd, 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<u8>) { pub fn send_packet(&self, cmd: PacketType, data: Vec<u8>) {
self.0.tx_connection.send((cmd, data)).unwrap(); self.0.tx_connection.send((cmd as u8, data)).unwrap();
} }
pub fn cache(&self) -> Option<&Arc<Cache>> { pub fn cache(&self) -> Option<&Arc<Cache>> {

View file

@ -2,6 +2,7 @@ use byteorder::{BigEndian, WriteBytesExt};
use std::io::Write; use std::io::Write;
use librespot_core::channel::ChannelData; use librespot_core::channel::ChannelData;
use librespot_core::packet::PacketType;
use librespot_core::session::Session; use librespot_core::session::Session;
use librespot_core::spotify_id::FileId; use librespot_core::spotify_id::FileId;
@ -13,7 +14,7 @@ pub fn get(session: &Session, file: FileId) -> ChannelData {
packet.write_u16::<BigEndian>(channel_id).unwrap(); packet.write_u16::<BigEndian>(channel_id).unwrap();
packet.write_u16::<BigEndian>(0).unwrap(); packet.write_u16::<BigEndian>(0).unwrap();
packet.write(&file.0).unwrap(); packet.write(&file.0).unwrap();
session.send_packet(0x19, packet); session.send_packet(PacketType::Image, packet);
data data
} }