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",
"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"

View file

@ -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::<BigEndian>(start as u32).unwrap();
data.write_u32::<BigEndian>(end as u32).unwrap();
session.send_packet(0x8, data);
session.send_packet(PacketType::StreamChunk, data);
channel
}

View file

@ -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"

View file

@ -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::<BigEndian>(seq).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_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<Result<Bytes, 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]);
error!("channel error: {} {}", packet.len(), code);

View file

@ -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())
}

View file

@ -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;

View file

@ -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,15 +155,18 @@ impl MercuryManager {
let mut pending = match pending {
Some(pending) => pending,
None if cmd == 0xb5 => MercuryPending {
None => {
if let PacketType::MercuryEvent = cmd {
MercuryPending {
parts: Vec::new(),
partial: None,
callback: None,
},
None => {
warn!("Ignore seq {:?} cmd {:x}", seq, cmd);
}
} else {
warn!("Ignore seq {:?} cmd {:x}", seq, cmd as u8);
return;
}
}
};
for i in 0..count {
@ -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;

View file

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

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 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<u8>) {
self.0.tx_connection.send((cmd, data)).unwrap();
pub fn send_packet(&self, cmd: PacketType, data: Vec<u8>) {
self.0.tx_connection.send((cmd as u8, data)).unwrap();
}
pub fn cache(&self) -> Option<&Arc<Cache>> {

View file

@ -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::<BigEndian>(channel_id).unwrap();
packet.write_u16::<BigEndian>(0).unwrap();
packet.write(&file.0).unwrap();
session.send_packet(0x19, packet);
session.send_packet(PacketType::Image, packet);
data
}